Meinung zu meiner Lernaufgabe

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
schmidicom
User
Beiträge: 3
Registriert: Montag 23. Oktober 2023, 09:44

Ich bin gerade dabei mittels der "Bootstrap Academy" Python zu lernen und habe meine erste Lernaufgabe endlich fertig (also "fertig" im Sinne von, jetzt kann man es hoffentlich anderen zumuten) und falls jemand Bock hat sich das zu geben könnte ich etwas Feedback dazu gebrauchen.

Code: Alles auswählen

import random

class Player:
    def __init__(self, id, name, symbol):
        self.id = id
        self.name = name
        self.symbol = symbol
        self.automatic = False

class Field:
    def __init__(self, id, name):
        self.id = id
        self.name = name
        self.content = " "

class Board:
    fields = []
    fields.append(Field(1, "oben links"))
    fields.append(Field(2, "oben mitte"))
    fields.append(Field(3, "oben rechts"))
    fields.append(Field(4, "mitte links"))
    fields.append(Field(5, "mitte"))
    fields.append(Field(6, "mitte rechts"))
    fields.append(Field(7, "unten links"))
    fields.append(Field(8, "unten mitte"))
    fields.append(Field(9, "unten rechts"))
    players = []
    players.append(Player(1, "Spieler 1", "X"))
    players.append(Player(2, "Spieler 2", "O"))
    current_player = players[0].id
    gameover = False
    winner = 0
    
    def change_player(self, id, new_name, new_symbol, automatic = False):
        self.players[id - 1].name = new_name
        self.players[id - 1].symbol = new_symbol
        self.players[id - 1].automatic = automatic

    def gen_list_free_fields(self, with_name = False):
        if not with_name:
            for field in self.fields:
                if field.content == " ":
                    yield field.id
        else:
            for field in self.fields:
                if field.content == " ":
                    yield (str(field.id) + ": " + str(field.name))

    def print_board(self):
        print("+---+---+---+")
        print(f'| {self.fields[0].content} | {self.fields[1].content} | {self.fields[2].content} |')
        print("+---+---+---+")
        print(f'| {self.fields[3].content} | {self.fields[4].content} | {self.fields[5].content} |')
        print("+---+---+---+")
        print(f'| {self.fields[6].content} | {self.fields[7].content} | {self.fields[8].content} |')
        print("+---+---+---+")

    def make_selection(self):
        self.print_board()
        selection_is_valid = False
        while not selection_is_valid:
            print("Die folgenden Auswahl steht zur Verfügung:")
            for field in list(self.gen_list_free_fields(True)):
                print(field)
            print("10: Um das Spiel zu beenden")

            if self.current_player == self.players[0].id:
                print(f'{self.players[0].name} wähle ein Feld aus (bitte Zahl eingeben):')
            else:
                print(f'{self.players[1].name} wähle ein feld aus (bitte Zahl eingeben):')

            try:
                player_selection = int(input())
            except ValueError:
                player_selection = -1
            
            if player_selection == 10:
                exit()

            for field in self.fields:
                if player_selection == field.id and field.content == " ":
                    selection_is_valid = True
                    break

            if not selection_is_valid:
                print("Deine Auswahl ist ungültig!")
        return player_selection - 1
    
    def make_automatic_selection(self):
        danger = 0
        if self.current_player == self.players[0].id:
            opponent_symbol = self.players[1].symbol
        else:
            opponent_symbol = self.players[0].symbol
        win1 = [self.fields[0], self.fields[1], self.fields[2]]
        win2 = [self.fields[3], self.fields[4], self.fields[5]]
        win3 = [self.fields[6], self.fields[7], self.fields[8]]
        win4 = [self.fields[0], self.fields[3], self.fields[6]]
        win5 = [self.fields[1], self.fields[4], self.fields[7]]
        win6 = [self.fields[6], self.fields[7], self.fields[8]]
        win7 = [self.fields[0], self.fields[4], self.fields[8]]
        win8 = [self.fields[2], self.fields[4], self.fields[6]]
        allwins = [win1, win2, win3, win4, win5, win6, win7, win8]
        for wins in allwins:
            for field in wins:
                if field.content == opponent_symbol:
                    danger = danger + 1
            if danger > 1:
                for field in wins:
                    if field.content == " ":
                        return field.id - 1
            danger = 0
        return random.choice(list(self.gen_list_free_fields())) - 1

    def make_turn(self):
        if self.current_player == self.players[0].id:
            if self.players[0].automatic == True:
                self.fields[self.make_automatic_selection()].content = self.players[0].symbol
            else:
                self.fields[self.make_selection()].content = self.players[0].symbol
            self.current_player = self.players[1].id
        else:
            if self.players[1].automatic == True:
                self.fields[self.make_automatic_selection()].content = self.players[1].symbol
            else:
                self.fields[self.make_selection()].content = self.players[1].symbol
            self.current_player = self.players[0].id

    def check_gameover(self):
        win1 = [self.fields[0], self.fields[1], self.fields[2]]
        win2 = [self.fields[3], self.fields[4], self.fields[5]]
        win3 = [self.fields[6], self.fields[7], self.fields[8]]
        win4 = [self.fields[0], self.fields[3], self.fields[6]]
        win5 = [self.fields[1], self.fields[4], self.fields[7]]
        win6 = [self.fields[6], self.fields[7], self.fields[8]]
        win7 = [self.fields[0], self.fields[4], self.fields[8]]
        win8 = [self.fields[2], self.fields[4], self.fields[6]]
        allwins = [win1, win2, win3, win4, win5, win6, win7, win8]
        for wins in allwins:
            if all(item.content == self.players[0].symbol for item in wins):
                self.gameover = True
                self.winner = self.players[0].id
                break
            elif all(item.content == self.players[1].symbol for item in wins):
                self.gameover = True
                self.winner = self.players[1].id
                break
        if not self.gameover and all(field.content != " " for field in self.fields):
            self.gameover = True
            self.winner = 0

    def run(self):
        while not self.gameover:
            self.make_turn()
            self.check_gameover()

        self.print_board()
        if self.winner == self.players[0].id:
            print(f'{self.players[0].name} hat gewonnen!')
        elif self.winner == self.players[1].id:
            print(f'{self.players[1].name} hat gewonnen!')
        elif self.winner == 0:
            print("Leider hat niemand gewonnen.")

if __name__ == "__main__":
    board = Board()
    print("Wie viele menschliche Spieler?:")
    correct_choice = False
    while not correct_choice:
        try:
            amount_players = int(input())
        except ValueError:
            amount_players = -1
        if not amount_players < 0 and not amount_players > 2:
            correct_choice = True
        else:
            print("Die Auswahl war ungültig, bitte gültige Auswahl eingeben.")
    match amount_players:
        case 0:
            board.change_player(1 ,"Computer 1", "X", True)
            board.change_player(2 ,"Computer 2", "O", True)
        case 1:
            board.change_player(2 ,"Computer", "O", True)
    board.run()
Sirius3
User
Beiträge: 17828
Registriert: Sonntag 21. Oktober 2012, 17:20

In der Klasse `Board` fehlt die __init__-Methode. Statt einer leeren Liste anzulegen und dann per append die Elemente hinzuzufügen kann man gleich die Liste mit den Elementen direkt erzeugen.
Die Geschichte mit der id ist schräg. Statt jedem Objekt händisch eine ID zu vergeben, benutzt man einfach die Objekte direkt.
Das macht eine Methode wie `make_turn` deutlich einfacher, weil man nicht Code kopieren muß.

Code: Alles auswählen

    def make_turn(self):
        if self.current_player.automatic:
            index = self.make_automatic_selection()
        else:
            index = self.make_selection()
        self.fields[index].content = self.current_player.symbol
Nebenbei, mit True vergleicht man nicht explizit.
Es ist auch nicht gut, erst zwei Player anzulegen, um sie später per change_player sie zu verändern. In Python vermeidet man, soweit es geht, Objekte zu verändern, sondern erzeugt einfach neue.

Bei Field ist es im Prinzip das selbe. Die ID ist einfach die Position in der fields-Liste, das name-Attribut wird gar nicht verwendet, so dass vom Field nur noch content überigbleibt, man kann also den String auch direkt in die fields-Liste schreiben, ohne dass man eine extra Klasse braucht.
Alles unter ` if __name__ == "__main__":` gehört in eine eigene Funktion, die man üblicherweise main nennt.

`match` ist kein Ersatz für ein einfaches `if`.
In `make_selection` wird `exit` verwendet, das gibt es offiziell gar nicht, und sollte in einem sauberen Programm auch nicht vorkommen. Ein Programm endet regulär, in dem die main-Funktion verlassen wird. Ein Flag versucht man zu vermeiden, in dem man eine while-True-Schleife schreibt, die per break verlassen wird.
Den Generator gen_list_free_fields in eine Liste umzuwandeln ist unnötig.
Das könnte also so aussehen:

Code: Alles auswählen

    def make_selection(self):
        self.print_board()
        while True:
            print("Die folgenden Auswahl steht zur Verfügung:")
            for field in self.gen_list_free_fields(True):
                print(field)
            print("10: Um das Spiel zu beenden")
            print(f'{self.current_player.name} wähle ein Feld aus (bitte Zahl eingeben):')
            try:
                player_selection = int(input())
            except ValueError:
                pass
            else:
                if player_selection == 10:
                    # Indikator, dass das Spiel beendet werden soll
                    return None
                player_selection -= 1
                if 0 < player_selection < len(self.fields):
                    if self.fields[player_selection]:
                        return player_selection
            print("Deine Auswahl ist ungültig!")
Die Felder, die zum gewinnen führen, definierst Du sowohl in make_automatic_selection als auch in `check_gameover`, das sollte man nur einmal im Code haben, Dank des ganzen Copy-Paste hast Du da auch einen Anpassfehler. Statt `danger` nachträglich auf 0 zu setzen, sollte man es einfach immer am Anfang der Schleife tun.
Das ganze könnte also so aussehen:

Code: Alles auswählen

    SERIES_INDICES = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 6],
        [8, 4, 8],
        [2, 4, 6],
    ]

    def make_automatic_selection(self):
        if self.current_player == self.players[0]:
            opponent_symbol = self.players[1].symbol
        else:
            opponent_symbol = self.players[0].symbol
        for indices in self.SERIES_INDICES:
            danger = sum(self.fields[i] == opponent_symbol for i in indices)
            if danger == 2:
                for i in indices:
                    if self.fields[i] == " ":
                        return i
        return random.choice([i for i, c in enumerate(self.fields) if c == " "])

    def check_gameover(self):
        for indices in self.SERIES_INDICES:
            for player in self.players:
                if all(self.fields[i] == player.symbol for i in indices):
                    self.gameover = True
                    self.winner = self.player
                    return
        if not any(f == " " for f in self.fields):
            self.gameover = True
            self.winner = None
Prinzipiell könnte man sich fragen, ob ein `Board` überhaupt das Spiel steuert, oder nur die Methoden bereitstellt, die direkt mit dem Brett zu tun haben.
Benutzeravatar
__blackjack__
User
Beiträge: 13242
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@schmidicom: Das Hauptprogramm sollte in einer Funktion stehen. Denn im moment werden da immer noch globale Variablen definiert, die man aus versehen irgendwo im Programm verwenden könnte, was zu schwer zu findenden Fehlern führen kann.

Warum haben Spieler und Felder ein `id`-Attribut? Das macht keinen Sinn. Objekte haben selbst eine Identität, und die ist dann auch schon von der Sprache her garantiert einmalig. Zum Beispiel der `current_player` sollte auf das `Player`-Objekt verweisen, nicht auf eine Zahl die für den Spieler steht von dem `Player`-Objekt als `id`-Attribut abgefragt wurde. Genau so bei den Feldern. Das ist einfach eine zusätzliche unnötige Indirektion.

Man erstellt keine Listen mit Werten in dem man erst eine leere Liste anlegt um dann gleich danach in x `apppend()`-Zeilen einzelne Werte hinzuzufügen.

Der `Board`-Klasse fehlt eine `__init__()`-Methode und die ganzen Attribute sind auf der *Klasse* definiert. Das ist falsch.

`correct_choice` ist überflüssig. Da würde man einfach eine ”Endlosschleife” schreiben die mit ``break`` verlassen wird, wenn die passende Bedingung eingetreten ist. Das gleiche gilt für `selection_is_valid`.

-1 sollte die Anzahl auch nie Annehmen, auch nicht als magische Zahl die etwas anzeigen soll. Irgendwann rutscht einem das dann doch mal durch und es wird irgendwo -1 als Anzahl verwendet. ``try``/``except`` kennt auch einen ``else``-Zweig.

``if not n < 0 and not n > 2:`` ist ziemlich umstädnlich für ``if 1 <= n <= 2:``.

`change_player()` ist keine gute Idee. Das `Board` sollte die Spieler als Argumente übergeben bekommen und nicht selbst Spieler erstellen die danach noch mal geändert werden.

``match`` ist hier IMHO ein Missbrauch. Das ist kein Ersetz für einfache ``if``/``elif``/``else`` sondern eine Anweisung um strukturelle Muster zu erkennen und Werte zuzuweisen. Wenn da in den ``cases`` keine Werte an Namen gebunden werden, dann ist dass das falsche Werkzeug.

`gameover` sollte kein Attribut sein. Die Methode die das setzt prüft ob das Spiel zuende ist. Die sollte das einfach als Ergebnis an den Aufrufer zurückgeben. Beziehungsweise noch besser: den Gewinner oder `None` zurückgeben — und der `winner` sollte auch kein Attribut sein. Und auch hier wieder: Das Objekt selbst, nicht eine indirekte ID. Durch diese Indirektionen gibt es da viel unsinnigen Code. Der gewinner ist die ID, dann werden die beiden Spieler auf die ID geprüft um dann den Namen von dem passenden Spieler auszugeben. Wenn man da einfach den Spieler direkt zurückgeben würde, braucht man überhaupt nicht prüfen welcher das ist, sondern kann direkt den Namen zurück geben.

Die `run()`-Methode würde ich vielleicht auch aus dem `Board` herausziehen, oder die Klasse in `Game` umbenennen.

`print_board()` enthält einiges was eigentlich eher in eine `__str__()`-Methode gehört, damit ``print(board)`` einfach funktioniert. Und `Field` könnte auch eine `__str__()`-Methode vertragen, damit man ausserhalb der Klasse nicht wissen muss wie das implementiert ist.

`win1` bis `win8` sollten so nicht existieren. Warum stehen diese Werte nicht gleich in der Liste sondern werden erst an nummerierte Namen gebunden?

Diese Liste(n) steh(t|en) zweimal im Programm. Das sollte nicht sein.

Diese elend vielen ``self.fields[…]`` sollten da auch nicht stehen. Nur die Nummern würden ja genügen. Und den Test für beide Spieler zu kopieren macht man auch nicht, da schreibt man eine Schleife über die beiden Spieler.

In `make_turn()` steht auch wieder zweimal der fast identische Code für beide Spieler.

Man vergleicht nicht explizit auf ``True`` (oder ``False``), denn bei dem Vergleich kommt ja wieder `True` oder `False` bei heraus, also den Wert, den man sowieso schon hatte und den man direkt verwenden kann. Oder dessen Gegenteil: dafür ist ``not`` da.

`danger` wird viel zu früh auf 0 gesetzt, recht weit von der Stelle entfernt wo es tatsächlich benutzt wird. Und es sollte *immer* vor dem aufaddieren initialisiert werden und nicht einmal ganz am Anfang und dann immer nach der Verwendung. Das ist komisch asymmetrisch und eine Zuweisung zu viel.

Bei der Auswahl der Felder ist auch wieder eine unnötige Indirektion. Statt den Index zurückzugeben, sollte man einfach das Feld zurückgeben. Gleiches gilt für `gen_list_free_fields()` im Default-Fall. Wobei das eigentlich zwei Funktionen sind, denn das `bool`-Argument entscheidet hier ja eigentlich komplett welcher Teil der Funktion ausgeführt wird.

`str()` und ``+`` um Zeichenketten und Werte zusammenzustückeln ist eher BASIC als Python. Du verwendest doch an anderer Stelle schon f-Zeichenkettenliterale.

`exit()` gibt es eigentlich gar nicht. Das ist ein Artefakt von der interaktiven Python-Shell. Man sollte die `exit()`-Funktion aus dem `sys`-Modul importieren. Wobei diese Funktion tiefer im Programm aufzurufen eigentlich ein No-Go ist, weil das einfach das Programm abbricht und hier auch eher wie ein ”Notausgang” aussieht um diesen Abbruchwunsch nicht sauber bis zur `main()` zurück zu kommunizieren, wo das Programm dann einfach aufhören kann. Zum Beispiel kann man im Moment im Hauptprogramm nicht einfach entscheiden ob man wirklich abbrechen möchte, oder nicht vielleicht noch eine neue Partie spielen möchte. `sys.exit()` sollte man in der Regel auch nur verwenden wenn man dem aufrufenden Programm zumindest potentiell einen anderen Rückgabecode als 0 übermitteln möchte. Denn *das* ist die eigentliche Aufgabe dieser Funktion.

Linear durch die Felder mit aufsteigender `id` zu gehen um das gewählte zu finden ist umständlich. Man kann doch einfach die gewählte ID verwenden um den Index in der Liste zu berechnen (- 1).

Was ich auch noch ändern würde wäre `Field.content`, denn das ist ja auch wieder redundant das die Felder das Symbol zum Spieler kennen, statt einfach den Besitzer zu speichern, der ja das Symbol als Attribut hat.

Komplett ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import random
import sys


class Player:
    def __init__(self, name, symbol, automatic):
        self.name = name
        self.symbol = symbol
        self.automatic = automatic


class Field:
    def __init__(self, name):
        self.name = name
        self.owner = None

    def __str__(self):
        return " " if self.is_empty else self.owner.symbol

    @property
    def is_empty(self):
        return self.owner is None


class Board:
    ALL_WINS = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [6, 7, 8],
        [0, 4, 8],
        [2, 4, 6],
    ]

    def __init__(self, players):
        self.fields = list(
            map(
                Field,
                [
                    "oben links",
                    "oben mitte",
                    "oben rechts",
                    "mitte links",
                    "mitte",
                    "mitte rechts",
                    "unten links",
                    "unten mitte",
                    "unten rechts",
                ],
            )
        )
        self.players = players
        self.current_player = self.players[0]

    def __str__(self):
        line = "+---+---+---+"
        return "\n".join(
            [
                line,
                f"| {self.fields[0]} | {self.fields[1]} | {self.fields[2]} |",
                line,
                f"| {self.fields[3]} | {self.fields[4]} | {self.fields[5]} |",
                line,
                f"| {self.fields[6]} | {self.fields[7]} | {self.fields[8]} |",
            ]
        )

    def make_selection(self):
        print(self)
        end_game_number = len(self.fields)
        while True:
            print("Die folgenden Auswahl steht zur Verfügung:")
            for number, field in enumerate(self.fields, 1):
                if field.is_empty:
                    print(f"{number}: {field.name}")
            print(f"{end_game_number}: Um das Spiel zu beenden")

            print(
                f"{self.current_player.name} wähle ein Feld aus"
                f" (bitte Zahl eingeben):"
            )

            try:
                player_selection = int(input())
            except ValueError:
                pass
            else:
                if player_selection == end_game_number:
                    sys.exit()  # TODO Rewrite without this hard exit.

                try:
                    field = self.fields[player_selection - 1]
                except IndexError:
                    pass
                else:
                    if field.is_empty:
                        return field

            print("Deine Auswahl ist ungültig!")

    def make_automatic_selection(self):
        for wins in self.ALL_WINS:
            fields = [self.fields[i] for i in wins]
            if (
                sum(
                    field.owner not in [None, self.current_player]
                    for field in fields
                )
                > 1
            ):
                field = next(
                    (field for field in fields if field.is_empty), None
                )
                if field:
                    return field

        return random.choice(
            [field for field in self.fields if field.is_empty]
        )

    def make_turn(self):
        (
            self.make_automatic_selection()
            if self.current_player.automatic
            else self.make_selection()
        ).owner = self.current_player

        self.current_player = (
            self.players[1]
            if self.current_player is self.players[0]
            else self.players[0]
        )

    def get_winner(self):
        for wins in self.ALL_WINS:
            for player in self.players:
                if all(self.fields[i].owner == player for i in wins):
                    return player

        return None

    def run(self):
        winner = None
        while not winner and any(field.is_empty for field in self.fields):
            self.make_turn()
            winner = self.get_winner()

        print(self)
        if winner:
            print(f"{winner.name} hat gewonnen!")
        else:
            print("Leider hat niemand gewonnen.")


def main():
    print("Wie viele menschliche Spieler?:")
    while True:
        try:
            human_player_count = int(input())
        except ValueError:
            pass
        else:
            if 1 <= human_player_count <= 2:
                break

        print("Die Auswahl war ungültig, bitte gültige Auswahl eingeben.")

    if human_player_count == 0:
        players = [
            Player("Computer 1", "X", True),
            Player("Computer 2", "O", True),
        ]
    else:
        players = [Player("Spieler 1", "X", False)]
        another_human = human_player_count == 2
        players.append(
            Player(
                "Spieler 2" if another_human else "Computer",
                "O",
                not another_human,
            )
        )
    assert players[0].symbol != players[1].symbol
    board = Board(players)
    board.run()


if __name__ == "__main__":
    main()
Das mit dem `current_player` wechseln könnte man noch schöner gestalten. Beispielsweise mit einem Index der zwischen 0 und 1 wechselt und einem `property()` für `current_player`.

Und das `automatic`-Flag ist auch eine Indirektion die ich loswerden wollen würde. Da könnte man eine Funktion für die Zugwahl direkt übergeben.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
schmidicom
User
Beiträge: 3
Registriert: Montag 23. Oktober 2023, 09:44

Danke für das Feedback, ich werde in Zukunft versuchen so viel davon wie möglich umzusetzen.

Auf alles davon kann ich jetzt nicht eingehen und das eine oder andere (zum Beispiel die Redundanz) ist mir nachträglich auch aufgefallen aber auf ein paar Punkte würde ich gerne eingehen.
Sirius3 hat geschrieben:Statt einer leeren Liste anzulegen und dann per append die Elemente hinzuzufügen kann man gleich die Liste mit den Elementen direkt erzeugen.
In der ersten Version die geschrieben hatte war es auch noch so, aber ich fand das die Lesbarkeit darunter zu sehr leidet und entschied mich dann für diesen Weg. Aber ich hätte es auch mit einem Backslash einfach auf mehrere Zeilen verteilen können ohne dafür "append" zu verwenden. Das wird in Zukunft anders aussehen.
Sirius3 hat geschrieben:Die Geschichte mit der id ist schräg. Statt jedem Objekt händisch eine ID zu vergeben, benutzt man einfach die Objekte direkt.
Anfangs wollte ich es ohne eine ID machen, habe es aber nicht hinbekommen, deshalb gab ich dann allen Objekten eine ID. Ist definitiv etwas das in Zukunft weg muss.
Sirius3 hat geschrieben:Nebenbei, mit True vergleicht man nicht explizit.
Verstehe ich jetzt nicht so ganz, was meinst du damit?
Sirius3 hat geschrieben:Es ist auch nicht gut, erst zwei Player anzulegen, um sie später per change_player sie zu verändern. In Python vermeidet man, soweit es geht, Objekte zu verändern, sondern erzeugt einfach neue.
Das war Absicht.
Das verändern der Spieler sollte ein komplett optionales Feature sein. Das ganze sollte so geschrieben sein das man es in einem anderen Script per "import" verwenden und je nach Bedarf anpassen kann bevor es effektiv ausgeführt wird.
Sirius3 hat geschrieben:Alles unter ` if __name__ == "__main__":` gehört in eine eigene Funktion, die man üblicherweise main nennt.
Auch das war Absicht aus dem bereits erwähnten Grund. Alles unter "if __name__ == "__main__":" diente nur dem Ausprobieren und sollte nur ausgeführt werden wenn das Script direkt aufgerufen wird.
Sirius3 hat geschrieben:In `make_selection` wird `exit` verwendet, das gibt es offiziell gar nicht, und sollte in einem sauberen Programm auch nicht vorkommen. Ein Programm endet regulär, in dem die main-Funktion verlassen wird. Ein Flag versucht man zu vermeiden, in dem man eine while-True-Schleife schreibt, die per break verlassen wird.
Ja das ist richtig schlecht gemacht, wollte nachträglich unbedingt eine Exit-Option einbauen. Werde ich in Zukunft auch anders machen.
Sirius3 hat geschrieben:Den Generator gen_list_free_fields in eine Liste umzuwandeln ist unnötig.
Wusste ich nicht, dachte das müsste man erst zu einer Liste machen.
Sirius3 hat geschrieben:Die Felder, die zum gewinnen führen, definierst Du sowohl in make_automatic_selection als auch in `check_gameover`, das sollte man nur einmal im Code haben, Dank des ganzen Copy-Paste hast Du da auch einen Anpassfehler. Statt `danger` nachträglich auf 0 zu setzen, sollte man es einfach immer am Anfang der Schleife tun.
Das kommt daher das der Computer als Gegner erst nachträglich hinzugekommen ist, in meinen ersten Versionen gab es das noch nicht. Aber ja, hätte ich besser machen können.

@__blackjack__
Einiges von dem was du geschrieben hast ist mir aktuell noch zu hoch...
Zum Beispiel:
__blackjack__ hat geschrieben:``if not n < 0 and not n > 2:`` ist ziemlich umstädnlich für ``if 1 <= n <= 2:``.
Meine Variante empfinde als einfacher, denn es entspricht doch exakt dem Satz "Es darf nicht kleiner als 0 und nicht grösser als 2 sein". Auch würde deine Variante doch dazu führen das die Auswahl 0 gar nicht mehr möglich wäre weil die Zahl 1 nicht kleiner oder gleich als 0 wäre.
karolus
User
Beiträge: 142
Registriert: Samstag 22. August 2009, 22:34

Auch würde deine Variante doch dazu führen das die Auswahl 0 gar nicht mehr möglich wäre weil die Zahl 1 nicht kleiner oder gleich als 0 wäre.
Auch du solltest da den offensichtlichen Typo erkennen und korrigieren können bevor du irgendwas mit class… schreibst.

``if 1 <= n <= 2:`` →→ für ``if 0 <= n <= 2:``
Sirius3
User
Beiträge: 17828
Registriert: Sonntag 21. Oktober 2012, 17:20

Das mit der Lesbarkeit kommt wahrscheinlich von dem Missverständnis, dass alles in einer Zeile stehen muss. Aber sobald ein Ausdruck in Klammern (auch eckige) steht, kann man ihn auf beliebig viele Zeilen aufteilen.
Optionale Features löst man dadurch, dass man dem Board zwar Spieler-Objekte übergeben kann, wenn nicht, dann erzeugt man die Default-Spieler.
Bei Objektorientierter Programmierung würde man auch nicht dem Board sagen, welcher Typ Spieler man ist, sondern der Spieler hätte eine Methode, den nächsten Zug auszuwählen, so kann man auch einfach verschiedene Computerspielerklassen mit unterschiedlichen Strategien implementieren.
Das mit dem if __name__ stimmt soweit ja auch, nur dass dort als einzige Zeile der Aufruf von main() steht.
`if 0 <= n <= 2:` entspricht exakt dem Satz `wenn n zwischen 0 und 2 ist` und ist damit noch einfacher zu verstehen.
Benutzeravatar
__blackjack__
User
Beiträge: 13242
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@schmidicom: Re Vergleich mit `True`: Nach ``if`` muss ein Ausdruck stehen, der zu wahr oder falsch ausgewertet wird. Bei ``if flag == True:`` wird `flag` mit `True` verglichen und das ergibt dann `True` wenn `flag` bereits den Wert `True` hat, und `False` wenn `flag` diesen Wert nicht hat. Also ist der Vergleich überflüssig, denn einfach `flag` ist dann ja schon `True` oder `False`, also ist das äquivalent zu ``if flag:``. Das ist als wenn man eine ganze Zahl irgendwo verwendet und da immer noch mal 0 drauf addiert ``x = start_value + 0``. Kann man machen, aber das macht keinen Sinn. (Ausser vielleicht bei Codegolf.)

Bei dem Test auf Spieleranzahl hatte ich noch nicht auf dem Schirm das es auch 0 Spieler, also zwei Computerspieler geben kann. Meistens ist an so einer Stelle ja nur die Frage ob man zu zweit spielt oder gegen den Rechner. Aber 0 finde ich cool.

Ich muss bei ``not n < 0 and not n > 2`` deutlich mehr Nachdenken als bei ``0 <= n <= 2``. Das ist die einfachste Art das auszudrücken, mit weniger Operationen, und ja auch das was man in der Mathematik an solchen Stellen immer sieht. Man könnte auch ``not (n < 0 or n > 2)`` oder ``n >= 0 and n <= 2`` schreiben, aber auch die Varianten haben immer noch mehr Operatoren.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Benutzeravatar
/me
User
Beiträge: 3557
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

schmidicom hat geschrieben: Mittwoch 1. November 2023, 09:40 Aber ich hätte es auch mit einem Backslash einfach auf mehrere Zeilen verteilen können ohne dafür "append" zu verwenden.
Es wurde zwar schon erwähnt, aber ich wollte noch einmal explizit darauf hinweisen, dass Code in der folgenden Form syntaktisch absolut in Ordnung ist:

Code: Alles auswählen

data = [
    23,
    42,
    57005
]
Innerhalb von Klammern brauchst du keinen Backslash am Zeilenende.
Antworten