Seite 1 von 1

Q-Learning am Beispiel 4-gewinnt

Verfasst: Montag 2. März 2020, 15:36
von Pedossi
Guten Tag zusammen!

Ich habe mich letztens damit beschäftigt, ein 4-gewinnt Spiel mit Python zu programmieren. Es ist lediglich in der Konsole, funktioniert aber auch soweit. Jetzt kam mir in den Sinn, dass ich dafür einen perfekten Gegenspieler programmiere, weil ich es faszinierend finde wie Computer "lernen" können. Allerdings verstehe ich auch nach mehreren Videos wie z.B. von Sentex und Websiten z.B. https://www.learndatasci.com nicht wie genau ich das jetzt auf mein eigenes Programm übertragen kann. In den Fällen, die ich gefunden habe, ist das Environment immer schon vorgegeben, was ich bei meinem Code nicht habe.

Also der Plan war es, zwei Agents gegeneinander spielen zu lassen und diese sich dabei immer weiter verbessern. Hierbei gibt es für jeden Zug -1 bei den Rewards, für eine Niederlage -100, für einen Sieg +100 und für ein Unentschieden +10. Jedoch verstehe ich nicht, wie genau ich dies in eine Q-Tabelle fassen soll und der Agent daraus lernt (bzw. die Formel dahinter). Ich hatte gedacht, dass für den State einfach das Feld nehmen könne.
Ich hoffe sehr darauf, dass ihr mir dabei helfen könntet Q-Learning an meinem Programm anzuwenden, bzw. mir Tipps zu geben wie es geht :D .

Das Spielfeld liegt folgend vor:

SpielInhalt = [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0,0, 0, 0], [0, 0, 0, 0,0, 0, 0]

, wobei jede Dimension eine Reihe darstellt. Je nach Spieler wird eine 1 bzw. 2 eingetragen.

Code: Alles auswählen

import random
import numpy as np
SpielInhalt = [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0,0, 0, 0], [0, 0, 0, 0,0, 0, 0]


SpielerListe = [[input("Spielername Spieler 1:"), 1], [input("Spielername Spieler 2:"), 2]]

# Setzen
ZugRecht = 0

def Zugfrage():
    Auswahl = True
    global ZugRecht, SpalteChoosen, Spieler
    Spieler = ZugRecht % 2
    while Auswahl:
        #SpalteChoosen = input("{}, du bist dran. Welche Spalte waehlst du (1-7)?".format(SpielerListe[Spieler][0]))
        SpalteChoosen = random.randrange(1, 8, 1)
        SpalteChoosen = str(SpalteChoosen)

        if SpalteChoosen.isdigit():
            SpalteChoosen = int(SpalteChoosen)
            if SpalteChoosen >= 1 and SpalteChoosen <= 7:
                Auswahl = False
                SpalteChoosen -= 1
                Platzieren()
            else:
                print("Deine Eingabe war nicht in dem genannten Bereich, versuche es erneut.")
        else:
            print("Die Eingabe war keine Zahl, versuche es erneut.")


def Platzieren():
    Feld = [0,0]
    global Spieler, SpalteChoosen, Auswahl, ZugRecht
    if SpielInhalt[0][SpalteChoosen] == 0:
        for Reihe in range(1, 7, 1):
            if SpielInhalt[Reihe - 1][SpalteChoosen] == 0:
                if Reihe == 6:
                    SpielInhalt[Reihe - 1][SpalteChoosen] = SpielerListe[Spieler][1]
                    Feld = [SpalteChoosen, Reihe - 1]

            else:
                SpielInhalt[Reihe - 2][SpalteChoosen] = SpielerListe[Spieler][1]
                Feld = [SpalteChoosen, Reihe - 2]
                break

        for FeldReihe in SpielInhalt:
            print(FeldReihe)
        print("---------------------")

    else:
        print("Das ist leider nicht moeglich. Probiere eine andere Spalte")
        Zugfrage()

    # Hat Reihe gewonnen?
    ReihenCounter = 0

    for NF_Reihe in range(Feld[0] - 3, Feld[0] + 4, 1):
        if not NF_Reihe <= -1 and not NF_Reihe >= 7:
            if SpielInhalt[Feld[1]][NF_Reihe] == SpielerListe[Spieler][1]:
                #print(NF_Reihe,Feld[1], SpielInhalt[Feld[1]][NF_Reihe], SpielerListe[Spieler][1], ReihenCounter+1 )
                ReihenCounter += 1
                if ReihenCounter >= 4:
                    break
            else:
                ReihenCounter = 0

    # Hat Spalte gewonnen?
    SpaltenCounter = 0

    for NF_Spalte in range(Feld[1] - 3, Feld[1] + 4, 1):
        if not NF_Spalte <= -1 and not NF_Spalte >= 6:
            if SpielInhalt[NF_Spalte][Feld[0]] == SpielerListe[Spieler][1]:
                SpaltenCounter += 1
                if SpaltenCounter >= 4:
                    break
            else:
                SpaltenCounter = 0

    # Hat Diagonale gewonnen?
    DiagonalenCounterOben = 0
    DiagonalenCounterUnten = 0

    for DiaOben in range(-3, 4, 1):
        if not Feld[0] + DiaOben <= -1 and not Feld[0] + DiaOben >= 7 and not Feld[1] + DiaOben <= -1 and not Feld[
                                                                                                                  1] + DiaOben >= 6:
            if SpielInhalt[Feld[1]+DiaOben][Feld[0]+DiaOben] == SpielerListe[Spieler][1]:
                DiagonalenCounterOben += 1
                if DiagonalenCounterOben >= 4:
                    break
            else:
                DiagonalenCounterOben = 0

    for DiaUnten in range(-3, 4, 1):
        if not Feld[0] - DiaUnten <= -1 and not Feld[0] - DiaUnten >= 7 and not Feld[1] + DiaUnten <= -1 and not Feld[
                                                                                                                  1] + DiaUnten >= 6:
            if SpielInhalt[Feld[1]+DiaUnten][Feld[0]-DiaUnten] == SpielerListe[Spieler][1]:
                DiagonalenCounterUnten += 1
                if DiagonalenCounterUnten >= 4:
                    break
            else:
                DiagonalenCounterUnten = 0

    # Gewonnen?
    if ReihenCounter >= 4 or SpaltenCounter >= 4 or DiagonalenCounterOben >= 4 or DiagonalenCounterUnten >= 4:
        print("{} hat gewonnen!".format(SpielerListe[Spieler][0]))


    else:
        fertig = True
        for i in range(0,6,1):
            for Elements in SpielInhalt[i]:
                if Elements == 0:
                    fertig = False
                    break
        if fertig:
            print("Unentschieden! Es gibt keinen Sieger")
        else:
            ZugRecht += 1
            Zugfrage()

Zugfrage()





Re: Q-Learning am Beispiel 4-gewinnt

Verfasst: Montag 2. März 2020, 19:17
von Sirius3
@Pedossi: bevor man anfängt, da neue Funktionalität zuzubauen, sollte man erstmal das jetzige aufräumen. Benutze kein `global`. Alles was eine Funktion braucht bekommt sie über ihre Argumente und Ergebnisse werden per `return` zurückgegeben. Du dagegen hast zwei Funktionen, die sich rekursiv gegenseitig aufrufen.
`Platzieren` ist dann auch viel zu lang, und sollte dringend in mehrere Funktionen aufgeteilt werden.

Da Du eh alles umschreiben mußt, kannst Du Dich auch gleich an die Namenskonventionen halten. Alle Variablen und Funktionen schreibt man klein_mit_unterstrich. Vermeide mischen von Englisch und Deutsch. Das ist schwierig zu lesen. Am besten alles englisch benennen.

Re: Q-Learning am Beispiel 4-gewinnt

Verfasst: Mittwoch 4. März 2020, 18:03
von Pedossi
Sirius3 hat geschrieben: Montag 2. März 2020, 19:17 @Pedossi: bevor man anfängt, da neue Funktionalität zuzubauen, sollte man erstmal das jetzige aufräumen. Benutze kein `global`. Alles was eine Funktion braucht bekommt sie über ihre Argumente und Ergebnisse werden per `return` zurückgegeben. Du dagegen hast zwei Funktionen, die sich rekursiv gegenseitig aufrufen.
`Platzieren` ist dann auch viel zu lang, und sollte dringend in mehrere Funktionen aufgeteilt werden.

Da Du eh alles umschreiben mußt, kannst Du Dich auch gleich an die Namenskonventionen halten. Alle Variablen und Funktionen schreibt man klein_mit_unterstrich. Vermeide mischen von Englisch und Deutsch. Das ist schwierig zu lesen. Am besten alles englisch benennen.
@Sirius3: Hallo Sirius! Vielen Dank für diesen Tipp, das ist keine schlechte Idee:

Code: Alles auswählen

round = 0
ingame = True

# Name Query
player_list = [[input("Spielername Spieler 1:"), 1], [input("Spielername Spieler 2:"), 2]]


def whose_turn():
    selection_process = True
    while selection_process:
        selected_column = input("{}, du bist dran. Welche Spalte waehlst du (1-7)?".format(player_list[player][0]))
        if selected_column.isdigit():
            selected_column = int(selected_column)
            if selected_column >= 1 and selected_column <= 7:
                selected_column -= 1
                selection_process = False
                return selected_column

            else:
                print("Deine Eingabe war nicht in dem genannten Bereich, versuche es erneut.")
        else:
            print("Die Eingabe war keine Zahl, versuche es erneut.")


def place(selected_column, character):
    if field[0][selected_column] == 0:
        for row in range(1, 7, 1):
            if field[row - 1][selected_column] == 0:
                if row == 6:
                    field[row - 1][selected_column] = character
                    occupied_field = [selected_column, row - 1]
                    for field_row in field:
                        print(field_row)
                    return occupied_field
            else:
                field[row - 2][selected_column] = character
                occupied_field = [selected_column, row - 2]
                for field_row in field:
                    print(field_row)
                return occupied_field

    else:
        return "failed"



def control_victory(occupied_field, player_turn, player_list):
    in_row_counter = 0
    in_column_counter = 0
    in_diagonal_top_to_bottom = 0
    in_diagonal_bottom_to_top = 0

    # Row won?
    for neighbouring_fields_in_row in range(occupied_field[0] - 3, occupied_field[0] + 4, 1):
        if not neighbouring_fields_in_row <= -1 and not neighbouring_fields_in_row >= 7:
            if field[occupied_field[1]][neighbouring_fields_in_row] == player_list[player][1]:
                in_row_counter += 1
                if in_row_counter >= 4:
                    break
            else:
                in_row_counter = 0

    # Column won?
    for neighbouring_fields_in_column in range(occupied_field[1] - 3, occupied_field[1] + 4, 1):
        if not neighbouring_fields_in_column <= -1 and not neighbouring_fields_in_column >= 6:
            if field[neighbouring_fields_in_column][occupied_field[0]] == player_list[player][1]:
                in_column_counter += 1
                if in_column_counter >= 4:
                    break
            else:
                in_column_counter = 0

    # Diagonal won?
    for nf_top_to_bottom in range(-3, 4, 1):
        if not occupied_field[0] + nf_top_to_bottom <= -1 and not occupied_field[0] + nf_top_to_bottom >= 7 and not occupied_field[1] + nf_top_to_bottom <= -1 and not occupied_field[
                                                                                                                  1] + nf_top_to_bottom >= 6:
            if field[occupied_field[1] + nf_top_to_bottom][occupied_field[0] + nf_top_to_bottom] == player_list[player][1]:
                in_diagonal_top_to_bottom += 1
                if in_diagonal_top_to_bottom >= 4:
                    break
            else:
                in_diagonal_top_to_bottom = 0
    for nf_bottom_to_top in range(-3, 4, 1):
        if not occupied_field[0] - nf_bottom_to_top <= -1 and not occupied_field[0] - nf_bottom_to_top >= 7 \
                and not occupied_field[1] + nf_bottom_to_top <= -1 and not occupied_field[1] + nf_bottom_to_top >= 6:
            if field[occupied_field[1] + nf_bottom_to_top][occupied_field[0] - nf_bottom_to_top] == player_list[player][1]:
                in_diagonal_bottom_to_top += 1
                if in_diagonal_bottom_to_top >= 4:
                    break
            else:
                in_diagonal_bottom_to_top = 0
    # Gewonnen?
    if in_row_counter >= 4 or in_column_counter >= 4 or in_diagonal_top_to_bottom >= 4 or in_diagonal_bottom_to_top >= 4:
        return 1
    else:
        return 0



while ingame:
    field = [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]
    if round % 2 == 0:
        player_turn = 0
    else:
        player_turn = 1

    done = False

    while not done:
        player = player_turn % 2
        failed = True

        while failed:
            place_returned_value = place(whose_turn(), player_list[player][1])

            if place_returned_value == "failed":
                print("Das ist leider nicht moeglich. Probiere eine andere Spalte")
            else:
                failed = False
                occupied_field = place_returned_value


        control_victory_returned_value = control_victory(occupied_field, player_turn, player_list)

        if control_victory_returned_value == 1:
            print("{} hat gewonnen!".format(player_list[player][0]))
            done = True

            continue_playing = input("Moechtet ihr nochmal spielen? 1 = Ja, 2 = Nein")
        else:
            draw = True
            for elements in range(0,6,1):
                for length in range(0, 7,1):
                    if field[elements][length] == 0:
                        draw = False

            if draw:
                print("Unentschieden! Keiner hat gewonnen.")
                done = True
                continue_playing = input("Moechtet ihr nochmal spielen? 1 = Ja, 2 = Nein: ")
            else:
                player_turn += 1

    if continue_playing == "1":
        round += 1
    else:
        ingame = False

Re: Q-Learning am Beispiel 4-gewinnt

Verfasst: Mittwoch 4. März 2020, 20:37
von __blackjack__
@Pedossi: Das mit dem kein ``global`` war etwas zu kurz gesprungen — nicht nur das Schlüsselwort nicht benutzen sondern auch tatsächlich keinen globalen Zustand verwenden. Alles was eine Funktion/Methode ausser Konstanten benötigt, sollte als Argument(e) übergeben werden. Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Du greifst mindestens auf `player`, `player_list`, und `field` in Funktionen zu ohne das diese Werte als Argument übergeben wurden.

`round` ist der Name einer eingebauten Funktion, den sollte man nicht an andere Werte binden.

Grunddatentypen haben in Namen nichts verloren. Den Typ ändert man öfter mal im Laufe der Programmentwicklung und dann hat man entweder falsche, irreführende Namen im Programm oder muss die überall anpassen.

Die Spieler sollten keine Listen sein, dafür sollte das Spielfeld kein Tupel sein. Listen sind für gleichartige Elemente wo die Indizes keine feste Bedeutung haben, und Tupel für Elemente von der Index an dem ein Wert steht eine Bedeutung hat. Bei den Spielern ist Index 0 der Name und Index 1 die Spielernummer. Beim Spielfeld ist an jedem Index eine Reihe des Spielfelds.

Auch für die Spieler würde ich dann aber schon kein normales Tupel mehr nehmen, denn diese magischen Indexwerte mit Bedeutung sind nicht besonders gut Lesbar. Also mindestens ein `collections.namedtuple` um den Elementen lesbare Namen zu geben, so dass es dann `player.name` statt `player[0]` heisst. Der Name `player` im Programm sollte besser `player_index` heissen wenn es nicht für den Player selbst, sondern für den Index in die `players` (ehemals `player_list`) steht.

Wobei die Zahlen 0, 1, und 2 auch ein wenig magisch und nichtssagend sind. Man kann da auch im Spielfeld `None` für unbelegt und die jeweiligen Spielerobjekte für belegte Felder ablegen und sich die indirektion über nichtssagende Zahlen sparen.

Die Flagvariablen bei ``while``-Schleifen kann man sich sparen wenn man aus den Schleifen ”Endlosschleifen” macht (``while True:``) und die dann gegebenenfalls mit ``break`` verlässt.

Die Nachfrage ob man noch eine Runde spielen möchte braucht dann auch nur *einmal* im Code stehen, und das auch noch näher an der Stelle wo die Antwort auch ausgewertet wird, was das Verständnis erleichtert.

Bei `whose_turn()` ist das Flag sowieso sinnlos denn gleich nachdem es gesetzt wird damit der nächste Schleifendurchlauf nicht stattfindet, wird die gesamte Funktion, und damit ja auch die Schleife, mit einem ``return`` verlassen.

Die äussere ``while``-Schleife in der Hauptfunktion könnte auch eine ``for``-Schleife über die Rundennummer sein statt oben das ``while`` und irgendwo ganz weit weg das ``round_number += 1`` zu haben, würde man diesen Zusammenhang gleich am Schleifenkopf ablesen können.

Ähnlich sieht das in der darin verschachtelten ``while``-Schleife aus, die eigentlich eine ``for``-Schleife mit `player_turn` als Laufvariable sein sollte. Oder noch direkter eine Schleife über eine endlose Folge von 0,1,0,1,…, also den Wert den `player` in der Schleife haben soll. Da `round_number` nirgends als Zahl gebraucht wird, ist eigentlich auch die äussere ``while``-Schleife eine die immer über False,True,False,True,… laufen müsste, wobei dieser Wert darüber entscheidet werd in der Runde anfängt.

Namen wie `place_returned_value` oder `control_victory_returned_value` sind nicht gut. Dass das der Wert ist den `control_victory()` zurückgegeben hat sieht man ja am Code, was den Leser interessiert ist was der denn *bedeutet*. „Control“ heisst auf Deutsch übrigens nicht „kontrollieren“ sondern „steuern“, ist also inhaltlich falsch. `check_victory()` oder `test_for_victory()` oder wäre passender. Die Funktion sollte auch nicht 0 oder 1 zurückgeben sondern `True` oder `False` damit der Leser nicht rätseln muss ob die Funktion vielleicht auch -23 oder 42 liefern kann.

`place_returned_value` frage ich mich warum das nicht gleich an den Namen `occupied_field` gebunden wird, wo der Wert am Ende ja sowieso landet.

`place()` hat übrigens einen Zweig in dem implizit `None` zurückgeben wird, womit der Aufrufende Code überhaupt nicht rechnet. Und eine Funktion sollte auch immer den gleichen ”duck type” zurückgeben. Statt Koordinaten als Tupel(!) oder eine Zeichenkette "failed" als Fehlerwert sollte man im Fehlerfall eine Ausnahme auslösen.

Die magischen Werte 6 und 7 sollten da nicht als Zahlen stehen. Die sind ja offensichtlich von der Länge von `field` abgeleitet und so sollte man das auch schreiben, damit man diese Länge ändern kann ohne überall im Programm suchen zu müssen wo man dann hart kodierte Zahlen ändern muss.

`whose_turn()` ist ein komischer Name für eine Funktion die den angegebenen Spieler fragt welche Spalte er nehmen möchte.

`check_victory()` bekommt `player_turn` übergeben, benutzt das aber gar nicht.

Namen sollte man nicht zu weit von der Stelle definieren an der sie verwendet werden. Das macht das Verständnis schwerer und auch das herausziehen von Funktion aus Code der zu lang geworden ist. In beiden Fällen muss man dann nämlich wieder nach oben suchen wo denn die erste Initialisierung im Code steht.

Das mit dem Funktionen herausziehen ist bei `check_victory()` relevant weil die IMHO zu lang und zu kompliziert ist.

Über einen Indexwert zu iterieren nur um den dann als Index in ein Sequenzobjekt zu benutzen ist in Python ein „anti pattern“. Man kann direkt über die Werte einer Sequenz iterieren, ohne den Umweg über einen Laufindex.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
from collections import namedtuple
from itertools import cycle

Player = namedtuple("Player", "name number")


def ask_column(field, players, player_index):
    while True:
        answer = input(
            f"{players[player_index].name}, du bist dran."
            f" Welche Spalte waehlst du (1-{len(field)})?"
        )
        if answer.isdigit():
            selected_column = int(answer) - 1
            if 0 <= selected_column < len(field):
                return selected_column
            print(
                "Deine Eingabe war nicht in dem genannten Bereich,"
                " versuche es erneut."
            )
        else:
            print("Die Eingabe war keine Zahl, versuche es erneut.")


def place(field, selected_column, player):
    if field[0][selected_column] != 0:
        raise ValueError(f"column {selected_column} is full")
    #
    # TODO Rewrite this without the need of code duplication and in a way that
    # clears the question what should happen if this loop never hits a
    # ``return``.
    #
    for row in range(1, len(field) + 1):
        if field[row - 1][selected_column] == 0:
            if row == len(field):
                field[row - 1][selected_column] = player
                for field_row in field:
                    print(field_row)
                return (selected_column, row - 1)
        else:
            field[row - 2][selected_column] = player
            for field_row in field:
                print(field_row)
            return (selected_column, row - 2)
    #
    # TODO What's this case? What should be returned here? If this is never
    # executed explain *why*.
    #
    assert False


def check_victory(field, latest_occupied_coordinate, players, player_index):
    column, row = latest_occupied_coordinate
    
    # Row won?
    in_row_counter = 0
    for neighbouring_fields_in_row in range(column - 3, column + 4):
        if (
            not neighbouring_fields_in_row <= -1
            and not neighbouring_fields_in_row > len(field)
        ):
            if (
                field[row][neighbouring_fields_in_row]
                == players[player_index].number
            ):
                in_row_counter += 1
                if in_row_counter >= 4:
                    break
            else:
                in_row_counter = 0

    # Column won?
    in_column_counter = 0
    for neighbouring_fields_in_column in range(row - 3, row + 4):
        if (
            not neighbouring_fields_in_column <= -1
            and not neighbouring_fields_in_column >= len(field)
        ):
            if (
                field[neighbouring_fields_in_column][column]
                == players[player_index].number
            ):
                in_column_counter += 1
                if in_column_counter >= 4:
                    break
            else:
                in_column_counter = 0

    # Diagonal won?
    in_diagonal_top_to_bottom = 0
    for nf_top_to_bottom in range(-3, 4):
        if (
            not column + nf_top_to_bottom < 0
            and not column + nf_top_to_bottom > len(field)
            and not row + nf_top_to_bottom < 0
            and not row + nf_top_to_bottom >= len(field)
        ):
            if (
                field[row + nf_top_to_bottom][column + nf_top_to_bottom]
                == players[player_index].number
            ):
                in_diagonal_top_to_bottom += 1
                if in_diagonal_top_to_bottom >= 4:
                    break
            else:
                in_diagonal_top_to_bottom = 0

    in_diagonal_bottom_to_top = 0
    for nf_bottom_to_top in range(-3, 4):
        if (
            not column - nf_bottom_to_top <= -1
            and not column - nf_bottom_to_top > len(field)
            and not row + nf_bottom_to_top <= -1
            and not row + nf_bottom_to_top >= len(field)
        ):
            if (
                field[row + nf_bottom_to_top][column - nf_bottom_to_top]
                == players[player_index].number
            ):
                in_diagonal_bottom_to_top += 1
                if in_diagonal_bottom_to_top >= 4:
                    break
            else:
                in_diagonal_bottom_to_top = 0

    return (
        in_row_counter >= 4
        or in_column_counter >= 4
        or in_diagonal_top_to_bottom >= 4
        or in_diagonal_bottom_to_top >= 4
    )


def main():
    players = [
        Player(input("Spielername Spieler 1:"), 1),
        Player(input("Spielername Spieler 2:"), 2),
    ]
    for does_player_2_start in cycle([False, True]):
        #
        # TODO Use `None` and `Player` objects instead of 0, 1, and 2.
        #
        field = [
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 0],
        ]

        player_indices = cycle([0, 1])
        if does_player_2_start:
            next(player_indices)

        for player_index in player_indices:
            while True:
                try:
                    latest_occupied_coordinate = place(
                        field,
                        ask_column(field, players, player_index),
                        players[player_index].number,
                    )
                except ValueError:
                    print(
                        "Das ist leider nicht moeglich."
                        " Probiere eine andere Spalte"
                    )
                else:
                    break

            if check_victory(
                field, latest_occupied_coordinate, players, player_index
            ):
                print("{} hat gewonnen!".format(players[player_index].name))
                break

            if all(all(cell != 0 for cell in column) for column in field):
                print("Unentschieden! Keiner hat gewonnen.")
                break

            player_turn += 1

        continue_playing = input(
            "Moechtet ihr nochmal spielen? 1 = Ja, 2 = Nein"
        )
        if continue_playing != "1":
            break


if __name__ == "__main__":
    main()
Alle Funktionen die `players` *und* `player_index` übergeben bekommen sollte man mal überprüfen ob sie noch einen anderen `Player` als den an `player_index` benutzen, denn dann brauchen die statt `players` und `player_index` ja eigentlich nur den `Player` selbst und damit ein Argument weniger.

Re: Q-Learning am Beispiel 4-gewinnt

Verfasst: Samstag 7. März 2020, 16:54
von Pedossi
Hallo blackjack!

Vielen Dank für deine sehr ausführliche und hilfreiche Antwort, ich habe mich lange damit beschäftigt.

Von vielen Sachen, die du erwähnt hattest, hatte ich bis jetzt noch nie gehört.

Allerdings hätte ich noch einige Fragen:

l.30: Warum ist Code.Wiederholung so schlimm wenn eine falsche Eingabe gemacht wurde? Ist das nicht der einfachste Weg wenn eine falsche Eingabe gemacht wurde?
l.50: Wofür ist das "assert False"? Abgesehen davon, dass ich das noch nie gehört habe, wird der Code doch niemals so weit kommen, da die if-Schleife immer mit einem return endet, oder?
l.142: Es wäre doch nicht sehr anschaulich wenn in der Konsole das Spielfeld aus ganz vielen "Nones" besteht, oder meinst du, dass dieses wieder zu einer 0 transferiert wird, sofern es geprintet wird?
l.183: Das ist noch ein Überbleibsel des alten Codes oder?

Der Rest ist jedoch sehr leicht verständlich, vielen Dank nochmal dafür. Ich bin immer froh darüber, neue Sachen kennenzulernen.

Re: Q-Learning am Beispiel 4-gewinnt

Verfasst: Samstag 7. März 2020, 20:52
von __blackjack__
@Pedossi: Code und Datenwiederholungen bergen immer die Gefahr, dass man bei Änderungen oder Fehlerbehebungen nicht alle kopierten Teile erwischt oder nicht alle auf die gleiche Art anpasst. Wenn man nicht kopiert sondern tatsächlich zweimal schreibt, hat man an der Stelle schon die Möglichkeit es falsch zu machen.

Endet die Schleife immer in einem ``return``? So rein an der Codestruktur gesehen ist das nicht garantiert! Man müsste also schon klären warum die Daten die da reingehen immer so aussehen, dass das ``assert`` tatsächlich nicht ausgeführt werden kann. Und selbst dann bleibt das ``assert``, denn das ist ja gerade für Sachen die eigentlich nie passieren dürften, damit man mitbekommt falls sie eben *doch* mal passieren und man sich auf Fehlersuche begeben kann.

Ich würde für leere Felder weder `None` noch 0 als sinnvolle Darstellung empfinden.

Upsi, ja, das `player_turn` habe ich übersehen. :oops: