Freigeisterhaus Foren-Übersicht
 FAQFAQ   SuchenSuchen   MitgliederlisteMitgliederliste   NutzungsbedingungenNutzungsbedingungen   BenutzergruppenBenutzergruppen   LinksLinks   RegistrierenRegistrieren 
 ProfilProfil   Einloggen, um private Nachrichten zu lesenEinloggen, um private Nachrichten zu lesen   LoginLogin 

Schachprogrammierung
Gehe zu Seite Zurück  1, 2
 
Neues Thema eröffnen   Neue Antwort erstellen   Drucker freundliche Ansicht    Freigeisterhaus Foren-Übersicht -> DAU's Paradise
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen  
Autor Nachricht
Casual3rdparty
dauerhaft gesperrt



Anmeldungsdatum: 21.07.2012
Beiträge: 6255

Beitrag(#1961546) Verfasst am: 31.10.2014, 21:37    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
step hat folgendes geschrieben:
alae hat folgendes geschrieben:
step hat folgendes geschrieben:
alae hat folgendes geschrieben:
Frage in die Runde: Ein Rochaderecht geht dauerhaft verloren, wenn der zugehörige König gezogen wird oder der zugehörige Turm gezogen wird oder ...?
Nur genau bei den genannten Bedingungen.
Ich glaube, ich verstehe deine Antwort nicht. Am Kopf kratzen

OK, noch ein Versuch: Ja, genau dann wenn mindestens eine der von Dir genannten Bedingungen eintritt.

Gesucht ist noch eine dritte Bedingung.
unter Schach, oder wenn ein Feld, über das die Rochade gehen würde bedroht ist.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961547) Verfasst am: 31.10.2014, 21:39    Titel: Antworten mit Zitat

Casual3rdparty hat folgendes geschrieben:
alae hat folgendes geschrieben:
step hat folgendes geschrieben:
alae hat folgendes geschrieben:
step hat folgendes geschrieben:
alae hat folgendes geschrieben:
Frage in die Runde: Ein Rochaderecht geht dauerhaft verloren, wenn der zugehörige König gezogen wird oder der zugehörige Turm gezogen wird oder ...?
Nur genau bei den genannten Bedingungen.
Ich glaube, ich verstehe deine Antwort nicht. Am Kopf kratzen

OK, noch ein Versuch: Ja, genau dann wenn mindestens eine der von Dir genannten Bedingungen eintritt.

Gesucht ist noch eine dritte Bedingung.
unter Schach, oder wenn ein Feld, über das die Rochade gehen würde bedroht ist.

Da wird die Rochade nur vorübergehend verhindert.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
NoReply
registrierter User



Anmeldungsdatum: 21.09.2013
Beiträge: 683

Beitrag(#1961548) Verfasst am: 31.10.2014, 21:40    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
Doch.

Und wenn ich es verrate, heißt es bestimmt: "Warum bin ich da nicht drauf gekommen?"


Schulterzucken

Wikipedia:
Zitat:
Zum sofortigen, endgültigen Verlust des Rochaderechts in einer Partie führt die folgende Spielsituation:

der König wurde in der Partie gezogen,
der an der Rochade beteiligte Turm wurde in der Partie gezogen. Der Verlust des Rochaderechts gilt für jeden Turm separat.

Zum vorübergehenden Verlust des Rochaderechts in einer Partie führt die folgende Spielsituation:

der König steht im Schach,
der König würde bei der Rochade ein bedrohtes Feld überqueren,
der König würde nach der Rochade im Schach stehen.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961549) Verfasst am: 31.10.2014, 21:43    Titel: Antworten mit Zitat

Ich sehe schon, da kommt keiner drauf.

Dritte Möglichkeit: Wenn der zugehörige Turm geschlagen wird.

Das dürfen wir nicht übersehen, sonst will unser Programm womöglich mit der Figur, die den Turm geschlagen hat, rochieren. zwinkern
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
NoReply
registrierter User



Anmeldungsdatum: 21.09.2013
Beiträge: 683

Beitrag(#1961550) Verfasst am: 31.10.2014, 21:43    Titel: Antworten mit Zitat

Oder meinst du wenn der Turm geschlagen wird?
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961551) Verfasst am: 31.10.2014, 21:44    Titel: Antworten mit Zitat

Das war fast gleichzeitig. Smilie
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961554) Verfasst am: 31.10.2014, 21:54    Titel: Antworten mit Zitat

step hat folgendes geschrieben:
Ich lese übrigens mit, hab sowas früher auch mal zum Üben gemacht, allerdings in C. Hat zwar funktioniert, war aber locker einen Faktor 1000 langsamer als die käuflichen Programme auf derselben HW.

Es würde mich interessieren, wie du damals einzelne Teilaufgaben angegangen bist. Hattest du zum Beispiel einen Transposition Table? Hast du bei der Minimax-Suche Alpha-Beta-Pruning angewendet?
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
step
registriert



Anmeldungsdatum: 17.07.2003
Beiträge: 22767
Wohnort: Germering

Beitrag(#1961555) Verfasst am: 31.10.2014, 22:00    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
Ich sehe schon, da kommt keiner drauf.

Dritte Möglichkeit: Wenn der zugehörige Turm geschlagen wird.

Das dürfen wir nicht übersehen, sonst will unser Programm womöglich mit der Figur, die den Turm geschlagen hat, rochieren. zwinkern

Das kann bei Deinem Programm aber nicht passieren, wenn ich es richtig gelesen habe.
_________________
Was ist der Sinn des Lebens? - Keiner, aber Leere ist Fülle für den, der sie sieht.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961556) Verfasst am: 31.10.2014, 22:02    Titel: Antworten mit Zitat

step hat folgendes geschrieben:
alae hat folgendes geschrieben:
Ich sehe schon, da kommt keiner drauf.

Dritte Möglichkeit: Wenn der zugehörige Turm geschlagen wird.

Das dürfen wir nicht übersehen, sonst will unser Programm womöglich mit der Figur, die den Turm geschlagen hat, rochieren. zwinkern

Das kann bei Deinem Programm aber nicht passieren, wenn ich es richtig gelesen habe.

Das kann nicht passieren, weil der Code für die Rochade ja noch fehlt. zwinkern
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
step
registriert



Anmeldungsdatum: 17.07.2003
Beiträge: 22767
Wohnort: Germering

Beitrag(#1961558) Verfasst am: 31.10.2014, 22:12    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
step hat folgendes geschrieben:
Ich lese übrigens mit, hab sowas früher auch mal zum Üben gemacht, allerdings in C. Hat zwar funktioniert, war aber locker einen Faktor 1000 langsamer als die käuflichen Programme auf derselben HW.
Es würde mich interessieren, wie du damals einzelne Teilaufgaben angegangen bist. Hattest du zum Beispiel einen Transposition Table?

Nein. Damals war ich der (fälschlichen) Ansicht, daß es reicht, sowas im Endspiel zu haben. Das hat sicherlich stark zu dem Performance-Unterschied beigetragen. Ich hab mal abgeschätzt, daß das im Mittelspiel etwa einen Faktor >= 50 ausgemacht hat.

alae hat folgendes geschrieben:
Hast du bei der Minimax-Suche Alpha-Beta-Pruning angewendet?

Ja, das war quasi der Aufhänger der ganzen Aktion, weil mich die alpha-Beta-Suche interessiert hat. Internet gabs da ja noch nicht so verbreitet, also habe ich versucht, in einer Schachzeitschrift was darüber zu lernen.

Ich habe dann ziemlich bald gemerkt, daß die käuflichen Schachprogramme nicht nur sehr viel Stellungswissen programmiert hatten, sondern auch daß die wohl low-level-optimiert waren im Vergleich zu einer Lehrbuch-alpha-beta-Suche. Damals waren auch die Compiler viel schlechter als Assembler usw.
_________________
Was ist der Sinn des Lebens? - Keiner, aber Leere ist Fülle für den, der sie sieht.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
step
registriert



Anmeldungsdatum: 17.07.2003
Beiträge: 22767
Wohnort: Germering

Beitrag(#1961559) Verfasst am: 31.10.2014, 22:14    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
Das kann nicht passieren, weil der Code für die Rochade ja noch fehlt. zwinkern

Gegnerische Figuren haben aber bei Dir doch negative Nummern.
_________________
Was ist der Sinn des Lebens? - Keiner, aber Leere ist Fülle für den, der sie sieht.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961560) Verfasst am: 31.10.2014, 22:23    Titel: Antworten mit Zitat

step hat folgendes geschrieben:
alae hat folgendes geschrieben:
step hat folgendes geschrieben:
Ich lese übrigens mit, hab sowas früher auch mal zum Üben gemacht, allerdings in C. Hat zwar funktioniert, war aber locker einen Faktor 1000 langsamer als die käuflichen Programme auf derselben HW.
Es würde mich interessieren, wie du damals einzelne Teilaufgaben angegangen bist. Hattest du zum Beispiel einen Transposition Table?

Nein. Damals war ich der (fälschlichen) Ansicht, daß es reicht, sowas im Endspiel zu haben. Das hat sicherlich stark zu dem Performance-Unterschied beigetragen. Ich hab mal abgeschätzt, daß das im Mittelspiel etwa einen Faktor >= 50 ausgemacht hat.

alae hat folgendes geschrieben:
Hast du bei der Minimax-Suche Alpha-Beta-Pruning angewendet?

Ja, das war quasi der Aufhänger der ganzen Aktion, weil mich die alpha-Beta-Suche interessiert hat. Internet gabs da ja noch nicht so verbreitet, also habe ich versucht, in einer Schachzeitschrift was darüber zu lernen.

Ich habe dann ziemlich bald gemerkt, daß die käuflichen Schachprogramme nicht nur sehr viel Stellungswissen programmiert hatten, sondern auch daß die wohl low-level-optimiert waren im Vergleich zu einer Lehrbuch-alpha-beta-Suche. Damals waren auch die Compiler viel schlechter als Assembler usw.

Wichtig beim Alpha-Beta-Pruning ist ja auch, dass man die Züge vorsortiert und dafür ist ein Transposition Table gut geeignet. Wenn man keinen hat, kann man auch Killerzüge nach vorn sortieren oder nach Most-Valuable-Victim-Least-Valuable-Aggressor sortieren.

Quiescence-Suche ist auch ganz hilfreich.

Das sind zumindest meine Erfahrungen, keine Ahnung, wie allgemeingültig die sind.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961562) Verfasst am: 31.10.2014, 22:27    Titel: Antworten mit Zitat

step hat folgendes geschrieben:
alae hat folgendes geschrieben:
Das kann nicht passieren, weil der Code für die Rochade ja noch fehlt. zwinkern

Gegnerische Figuren haben aber bei Dir doch negative Nummern.

Ja, aber ich werde gar nicht prüfen, ob da wirklich ein Turm steht. Wenn das Rochaderecht gegeben ist, die Felder dazwischen leer sind und der König nicht aus, über oder ins Schach rochiert, dann lasse ich die Rochade zu.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
step
registriert



Anmeldungsdatum: 17.07.2003
Beiträge: 22767
Wohnort: Germering

Beitrag(#1961563) Verfasst am: 31.10.2014, 22:34    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
Wichtig beim Alpha-Beta-Pruning ist ja auch, dass man die Züge vorsortiert und dafür ist ein Transposition Table gut geeignet. Wenn man keinen hat, kann man auch Killerzüge nach vorn sortieren oder nach Most-Valuable-Victim-Least-Valuable-Aggressor sortieren.

Soweit ich mich erinnere, habe ich einfach den Zug nach vorn sortiert, der bei der letzten Bewertung derselben Frabe (also zwei Halbzüge zuvor) die höchste Bewertung hatte. Ist aber sicher viel zu simpel.

alae hat folgendes geschrieben:
Quiescence-Suche ist auch ganz hilfreich.

Das ist mir zugegeben neu. Zumindest kenne ich den Ausdruck nicht.
_________________
Was ist der Sinn des Lebens? - Keiner, aber Leere ist Fülle für den, der sie sieht.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
step
registriert



Anmeldungsdatum: 17.07.2003
Beiträge: 22767
Wohnort: Germering

Beitrag(#1961565) Verfasst am: 31.10.2014, 22:39    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
step hat folgendes geschrieben:
alae hat folgendes geschrieben:
Das kann nicht passieren, weil der Code für die Rochade ja noch fehlt. zwinkern
Gegnerische Figuren haben aber bei Dir doch negative Nummern.
Ja, aber ich werde gar nicht prüfen, ob da wirklich ein Turm steht. Wenn das Rochaderecht gegeben ist, die Felder dazwischen leer sind und der König nicht aus, über oder ins Schach rochiert, dann lasse ich die Rochade zu.

Hmm ... aber betsteht die primäre Prüfung des Rochaderechts nicht gerade darin, daß König und Turm auf ihren Anfangsfeldern stehen?
_________________
Was ist der Sinn des Lebens? - Keiner, aber Leere ist Fülle für den, der sie sieht.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961567) Verfasst am: 31.10.2014, 22:47    Titel: Antworten mit Zitat

step hat folgendes geschrieben:
alae hat folgendes geschrieben:
Quiescence-Suche ist auch ganz hilfreich.

Das ist mir zugegeben neu. Zumindest kenne ich den Ausdruck nicht.

Da geht es darum, ab einer bestimmten Suchtiefe nur noch Schlagzüge zu prüfen, dann aber mit unbegrenzter Tiefe. Das hilft gegen das Horizont-Problem.
https://chessprogramming.wikispaces.com/Quiescence+Search
https://chessprogramming.wikispaces.com/Horizon+Effect

step hat folgendes geschrieben:
alae hat folgendes geschrieben:
step hat folgendes geschrieben:
alae hat folgendes geschrieben:
Das kann nicht passieren, weil der Code für die Rochade ja noch fehlt. zwinkern
Gegnerische Figuren haben aber bei Dir doch negative Nummern.
Ja, aber ich werde gar nicht prüfen, ob da wirklich ein Turm steht. Wenn das Rochaderecht gegeben ist, die Felder dazwischen leer sind und der König nicht aus, über oder ins Schach rochiert, dann lasse ich die Rochade zu.

Hmm ... aber betsteht die primäre Prüfung des Rochaderechts nicht gerade darin, daß König und Turm auf ihren Anfangsfeldern stehen?

So wie ich das geplant habe, muss ich das gar nicht überprüfen. Aber morgen poste ich den Code, vielleicht wird es dann klar. zwinkern
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961600) Verfasst am: 01.11.2014, 10:37    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
self.castling ist eine Liste mit Rochaderechten, der Reihe nach weiße kurze Rochade, weiße lange Rochade, schwarze kurze Rochade und schwarze lange Rochade. Die Rochaderechte werden nur auf False gesetzt, wenn sie dauerhaft verloren gehen, sie bleiben True, wenn die Rochade nur vorrübergehend nicht möglich ist.

Frage in die Runde: Ein Rochaderecht geht dauerhaft verloren, wenn der zugehörige König gezogen wird oder der zugehörige Turm gezogen wird oder [der zugehörige Turm geschlagen wird]?

Ich schreibe make_move jetzt so um, dass gegebenenfalls Rochaderechte auf False gesetzt werden.

Code:

    def make_move(self, origin, target, promotion=None):
        move = Position(self)
        if promotion is not None:
            move.board[target] = promotion
        else:
            move.board[target] = move.board[origin]
        move.board[origin] = 0
        if origin == 95 or origin == 98 or target == 98:
            move.castling[0] = False
        if origin == 95 or origin == 91 or target == 91:
            move.castling[1] = False
        if origin == 25 or origin == 28 or target == 28:
            move.castling[2] = False
        if origin == 25 or origin == 21 or target == 21:
            move.castling[3] = False
        return move

Ich muss gar nicht prüfen, ob auf den Feldern wirklich ein König bzw. Turm steht. Denn wenn irgendeine andere Figur z.B. auf 98 steht, dann muss castling[0] bereits False sein und ich überschreibe nur False mit False.

Jetzt kann ich generate_moves um den Rochade-Code ergänzen

Code:

        # Rochade
        if self.player == 1:
            if self.castling[0] and self.board[96] == 0 and self.board[97] == 0 and not self.attacked(95, -1) and not self.attacked(96, -1) and not self.attacked(97, -1):
                move = self.make_move(95, 97)
                move.board[96] = 2
                move.board[98] = 0
                moves.append(move)
            if self.castling[1] and self.board[94] == 0 and self.board[93] == 0 and self.board[92] == 0 and not self.attacked(95, -1) and not self.attacked(94, -1) and not self.attacked(93, -1):
                move = self.make_move(95, 93)
                move.board[94] = 2
                move.board[91] = 0
                moves.append(move)
        else:
            if self.castling[2] and self.board[26] == 0 and self.board[27] == 0 and not self.attacked(25, 1) and not self.attacked(26, 1) and not self.attacked(27, 1):
                move = self.make_move(25, 27)
                move.board[26] = -2
                move.board[28] = 0
                moves.append(move)
            if self.castling[3] and self.board[24] == 0 and self.board[23] == 0 and self.board[22] == 0 and not self.attacked(25, 1) and not self.attacked(24, 1) and not self.attacked(23, 1):
                move = self.make_move(25, 23)
                move.board[24] = -2
                move.board[21] = 0
                moves.append(move)

Auch hier muss ich nicht prüfen, ob auf den entsprechenden Feldern wirklich ein König bzw. Turm steht. Wenn das entsprechende Rochaderecht gegeben ist, dann ist das bereits eine Garantie, dass König und Turm noch da stehen, wo sie sollen. Ich muss dann nur noch schauen, ob irgendwas vorübergehend gegen die Rochade spricht.

Aber mal angenommen, ich hätte vergessen, das Schlagen von Türmen zu berücksichtigen. Dann könnte z.B. ein Läufer einen gegnerischen Turm schlagen, das Programm hält die Rochade immer noch für möglich und könnte mit dem Läufer rochieren.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961635) Verfasst am: 01.11.2014, 15:13    Titel: Antworten mit Zitat

Unser Zuggenerator kennt jetzt schon alle Zugregeln, aber damit ein Zug gültig ist, darf der eigene König nachher nicht im Schach stehen, was wir bisher noch gar nicht berücksichtigen. Das zu prüfen ist aber jetzt trivial, da wir schon für die Rochade eine Methode attacked geschrieben haben. Wir müssen jetzt nur den König finden und schauen, ob er angegriffen wird.

Code:

    def king_in_check(self, defender):
        for i in range(64):
            target = long_index[i]
            if defender * self.board[target] == 6:
                return self.attacked(target, -defender)
   
    def legal(self):
        return not self.king_in_check(-self.player)


Im Zuggenerator müssen wir jetzt jeden Zug überprüfen und dürfen nur die gültigen zur Liste moves hinzufügen:

Code:

    def generate_moves(self):
        moves = []
        for i in range(64):
            origin = long_index[i]
            if self.movable(origin):
                # König
                if abs(self.board[origin]) == 6:
                    for offset in king_offsets:
                        target = origin + offset
                        if self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                # Dame
                elif abs(self.board[origin]) == 5:
                    for offset in queen_offsets:
                        target = origin + offset
                        while self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if self.capturable(target):
                                break
                            target += offset
                # Läufer
                elif abs(self.board[origin]) == 4:
                    for offset in bishop_offsets:
                        target = origin + offset
                        while self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if self.capturable(target):
                                break
                            target += offset
                # Springer
                elif abs(self.board[origin]) == 3:
                    for offset in knight_offsets:
                        target = origin + offset
                        if self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                # Turm
                elif abs(self.board[origin]) == 2:
                    for offset in rook_offsets:
                        target = origin + offset
                        while self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if self.capturable(target):
                                break
                            target += offset
                # weißer Bauer
                elif self.board[origin] == 1:
                    target = origin - 10
                    if self.empty(target):
                        if target // 10 == 2:
                            for promotion in (5, 4, 3, 2):
                                move = self.make_move(origin, target, promotion)
                                if move.legal():
                                    moves.append(move)
                        else:
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if origin // 10 == 8:
                                target -= 10
                                if self.empty(target):
                                    move = self.make_move(origin, target)
                                    move.en_passant = target
                                    if move.legal():
                                        moves.append(move)
                    for offset in white_pawn_offsets:
                        target = origin + offset
                        if self.capturable(target):
                            if target // 10 == 2:
                                for promotion in (5, 4, 3, 2):
                                    move = self.make_move(origin, target, promotion)
                                    move.en_passant = target
                                    if move.legal():
                                        moves.append(move)
                            else:
                                move = self.make_move(origin, target)
                                if move.legal():
                                    moves.append(move)
                # schwarzer Bauer
                elif self.board[origin] == -1:
                    target = origin + 10
                    if self.empty(target):
                        if target // 10 == 9:
                            for promotion in (-5, -4, -3, -2):
                                move = self.make_move(origin, target, promotion)
                                if move.legal():
                                    moves.append(move)
                        else:
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if origin // 10 == 3:
                                target += 10
                                if self.empty(target):
                                    move = self.make_move(origin, target)
                                    move.en_passant = target
                                    if move.legal():
                                        moves.append(move)
                    for offset in black_pawn_offsets:
                        target = origin + offset
                        if self.capturable(target):
                            if target // 10 == 9:
                                for promotion in (-5, -4, -3, -2):
                                    move = self.make_move(origin, target, promotion)
                                    if move.legal():
                                        moves.append(move)
                            else:
                                move = self.make_move(origin, target)
                                if move.legal():
                                    moves.append(move)
        # Rochade
        if self.player == 1:
            if self.castling[0] and self.board[96] == 0 and self.board[97] == 0 and not self.attacked(95, -1) and not self.attacked(96, -1) and not self.attacked(97, -1):
                move = self.make_move(95, 97)
                move.board[96] = 2
                move.board[98] = 0
                if move.legal():
                    moves.append(move)
            if self.castling[1] and self.board[94] == 0 and self.board[93] == 0 and self.board[92] == 0 and not self.attacked(95, -1) and not self.attacked(94, -1) and not self.attacked(93, -1):
                move = self.make_move(95, 93)
                move.board[94] = 2
                move.board[91] = 0
                if move.legal():
                    moves.append(move)
        else:
            if self.castling[2] and self.board[26] == 0 and self.board[27] == 0 and not self.attacked(25, 1) and not self.attacked(26, 1) and not self.attacked(27, 1):
                move = self.make_move(25, 27)
                move.board[26] = -2
                move.board[28] = 0
                if move.legal():
                    moves.append(move)
            if self.castling[3] and self.board[24] == 0 and self.board[23] == 0 and self.board[22] == 0 and not self.attacked(25, 1) and not self.attacked(24, 1) and not self.attacked(23, 1):
                move = self.make_move(25, 23)
                move.board[24] = -2
                move.board[21] = 0
                if move.legal():
                    moves.append(move)
        # en passant
        if self.en_passant is not None:
            if self.player == 1:
                target = self.en_passant - 10
                origin = self.en_passant - 1
                if self.board[origin] == 1:
                    move = self.make_move(origin, target)
                    move.board[self.en_passant] = 0
                    if move.legal():
                        moves.append(move)
                origin = self.en_passant + 1
                if self.board[origin] == 1:
                    move = self.make_move(origin, target)
                    move.board[self.en_passant] = 0
                    if move.legal():
                        moves.append(move)
            else:
                target = self.en_passant + 10
                origin = self.en_passant - 1
                if self.board[origin] == -1:
                    move = self.make_move(origin, target)
                    move.board[self.en_passant] = 0
                    if move.legal():
                        moves.append(move)
                origin = self.en_passant + 1
                if self.board[origin] == -1:
                    move = self.make_move(origin, target)
                    move.board[self.en_passant] = 0
                    if move.legal():
                        moves.append(move)
        return moves


Und jetzt ist unser Zuggenerator vollständig. Drei von vier Teilaufgaben haben wir damit erledigt. Ich sag ja, so kompliziert ist es nicht. zwinkern
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
step
registriert



Anmeldungsdatum: 17.07.2003
Beiträge: 22767
Wohnort: Germering

Beitrag(#1961636) Verfasst am: 01.11.2014, 15:24    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
Und jetzt ist unser Zuggenerator vollständig. Drei von vier Teilaufgaben haben wir damit erledigt. Ich sag ja, so kompliziert ist es nicht. zwinkern

Hast Du schon berücksichtigt, daß das Spiel bei Matt oder Patt oder 3maliger Zugwiederholung zuende ist?
_________________
Was ist der Sinn des Lebens? - Keiner, aber Leere ist Fülle für den, der sie sieht.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961638) Verfasst am: 01.11.2014, 15:29    Titel: Antworten mit Zitat

step hat folgendes geschrieben:
alae hat folgendes geschrieben:
Und jetzt ist unser Zuggenerator vollständig. Drei von vier Teilaufgaben haben wir damit erledigt. Ich sag ja, so kompliziert ist es nicht. zwinkern

Hast Du schon berücksichtigt, daß das Spiel bei Matt oder Patt oder 3maliger Zugwiederholung zuende ist?

Bei Matt oder Patt führt jeder Zug dazu, dass der eigene König im Schach steht. Solche Stellungen haben deshalb keine gültigen Züge und generate_moves gibt eine leere Liste zurück.

Dreimalige Zugwiederholung wird noch nicht berücksichtigt.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
step
registriert



Anmeldungsdatum: 17.07.2003
Beiträge: 22767
Wohnort: Germering

Beitrag(#1961639) Verfasst am: 01.11.2014, 15:32    Titel: Antworten mit Zitat

alae hat folgendes geschrieben:
Bei Matt oder Patt führt jeder Zug dazu, dass der eigene König im Schach steht. Solche Stellungen haben deshalb keine gültigen Züge und generate_moves gibt eine leere Liste zurück.

Schon klar, aber die Bewertung dieser Situationen unterscheidet sich ja fundamental, je nachdem ob der eigene König bereits im Schach stand oder nicht. In der Positionsbewertung müßte daher mE eine Ausnahme für die Pattsituation gemacht werden.
_________________
Was ist der Sinn des Lebens? - Keiner, aber Leere ist Fülle für den, der sie sieht.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961642) Verfasst am: 01.11.2014, 15:40    Titel: Antworten mit Zitat

step hat folgendes geschrieben:
alae hat folgendes geschrieben:
Bei Matt oder Patt führt jeder Zug dazu, dass der eigene König im Schach steht. Solche Stellungen haben deshalb keine gültigen Züge und generate_moves gibt eine leere Liste zurück.

Schon klar, aber die Bewertung dieser Situationen unterscheidet sich ja fundamental, je nachdem ob der eigene König bereits im Schach stand oder nicht. In der Positionsbewertung müßte daher mE eine Ausnahme für die Pattsituation gemacht werden.

Ich habe vor, das später in der Minimax-Suche zu berücksichtigen.

Die Positionsbewertung ist momentan noch ziemlich "dumm" und bewertet auch Matt und Patt stur nach den Piece-Square-Tables.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
Kival
Profeminist Ghost



Anmeldungsdatum: 14.11.2006
Beiträge: 24071

Beitrag(#1961655) Verfasst am: 01.11.2014, 17:35    Titel: Antworten mit Zitat

Vielleicht nicht uninteressant für euch:

http://andrewgelman.com/2014/10/27/really-often-easier-win-rematch-defend-championship/

Andrew Gelman hat folgendes geschrieben:
Is it really “often easier to win a rematch than to defend a championship”?

The quoted bit above comes from Tyler Cowen, writing about the Anand/Carlsen world championship rematch. I’m still not used to the idea of a new world championship match every year but I guess why not?

(...)

Chess piece survival rates: some graphical possibilities here

_________________
"A basic literacy in statistics will one day be as necessary for efficient citizenship as the ability to read and write." (angeblich H. G. Wells)
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1961748) Verfasst am: 02.11.2014, 10:24    Titel: Antworten mit Zitat

Heute ein bisschen Code, der uns helfen wird, Remis zu erkennen.

1. Ich gebe den Position-Objekten eine "Halbzuguhr", die mitzählt, wie lange schon kein Bauer gezogen und keine Figur geschlagen wurde. Die Aufgabe, die Halbzuguhr hochzuzählen bzw. auf 0 zurückzusetzen überlasse ich make_move

Code:

    def __init__(self, position=None):
        if position is not None:
            self.board = list(position.board)
            self.player = -position.player
            self.castling = list(position.castling)
            self.en_passant = None
            self.halfmove_clock = position.halfmove_clock
        else:
            self.board = [
                -7, -7, -7, -7, -7, -7, -7, -7, -7, -7,
                -7, -7, -7, -7, -7, -7, -7, -7, -7, -7,
                -7, -2, -3, -4, -5, -6, -4, -3, -2, -7,
                -7, -1, -1, -1, -1, -1, -1, -1, -1, -7,
                -7,  0,  0,  0,  0,  0,  0,  0,  0, -7,
                -7,  0,  0,  0,  0,  0,  0,  0,  0, -7,
                -7,  0,  0,  0,  0,  0,  0,  0,  0, -7,
                -7,  0,  0,  0,  0,  0,  0,  0,  0, -7,
                -7,  1,  1,  1,  1,  1,  1,  1,  1, -7,
                -7,  2,  3,  4,  5,  6,  4,  3,  2, -7,
                -7, -7, -7, -7, -7, -7, -7, -7, -7, -7,
                -7, -7, -7, -7, -7, -7, -7, -7, -7, -7,
            ]
            self.player = 1
            self.castling = [True, True, True, True]
            self.en_passant = None
            self.halfmove_clock = 0


Code:

    def make_move(self, origin, target, promotion=None):
        move = Position(self)
        if move.board[origin] == 1 or move.board[origin] == -1 or move.board[target] != 0:
            move.halfmove_clock = 0
        else:
            move.halfmove_clock += 1
        if promotion is not None:
            move.board[target] = promotion
        else:
            move.board[target] = move.board[origin]
        move.board[origin] = 0
        if origin == 95 or origin == 98 or target == 98:
            move.castling[0] = False
        if origin == 95 or origin == 91 or target == 91:
            move.castling[1] = False
        if origin == 25 or origin == 28 or target == 28:
            move.castling[2] = False
        if origin == 25 or origin == 21 or target == 21:
            move.castling[3] = False
        return move


2. Ich überlade "==", damit wir festellen können, ob zwei Stellungen gleich sind.

Code:

    def __eq__(self, other):
        if self.board != other.board:
            return False
        if self.player != other.player:
            return False
        if self.castling != other.castling:
            return False
        if self.en_passant != other.en_passant:
            return False
        return True


3. Eine weitere Regel besagt, dass eine Partie Remis ist, wenn kein Matt mehr möglich ist, selbst wenn beide Spieler zusammenhelfen. Eine Funktion zu schreiben, die für beliebige Stellungen entscheiden kann, ob ein Matt möglich ist, ist aber alles andere als trivial. Es würde mich nicht wundern, wenn auch manche kommerzielle Programme nur eine echte Teilmenge dieser Stellungen erkennen können. Auch wir geben uns mit folgenden Teilmengen zufrieden:

- König gegen König
- König+Springer gegen König
- König gegen König und beliebig viele Läufer beider Spieler auf derselben Feldfarbe.

Code:

    def checkmate_impossible(self):
        knights = 0
        light_square_bishops = 0
        dark_square_bishops = 0
        for i in range(64):
            square = long_index[i]
            if abs(self.board[square]) in (5, 2, 1):
                return False
            elif abs(self.board[square]) == 4:
                if i % 2 == i // 8 % 2:
                    light_square_bishops += 1
                else:
                    dark_square_bishops += 1
            elif abs(self.board[square]) == 3:
                knights += 1
                if knights > 1:
                    return False
        if knights == 1 and light_square_bishops + black_square_bishops == 0:
            return True
        elif knights == 0 and (light_square_bishops == 0 or black_square_bishops == 0):
            return True
        else:
            return False
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1962323) Verfasst am: 04.11.2014, 19:22    Titel: Antworten mit Zitat

Noch ne Kleinigkeit: Ich gebe jeder Stellung einen String, der den Zug beschreibt, der zu dieser Stellung geführt hat. Der String besteht einfach nur aus Start- und Zielfeld, z.B. "e2e4". Bei Bauernumwandlungen wird noch die neue Figur angehängt, z.B. "e7e8D".

Code:

    def make_move(self, origin, target, promotion=None):
        move = Position(self)
        if move.board[origin] == 1 or move.board[origin] == -1 or move.board[target] != 0:
            move.halfmove_clock = 0
        else:
            move.halfmove_clock += 1
        if promotion is not None:
            move.board[target] = promotion
        else:
            move.board[target] = move.board[origin]
        move.board[origin] = 0
        if origin == 95 or origin == 98 or target == 98:
            move.castling[0] = False
        if origin == 95 or origin == 91 or target == 91:
            move.castling[1] = False
        if origin == 25 or origin == 28 or target == 28:
            move.castling[2] = False
        if origin == 25 or origin == 21 or target == 21:
            move.castling[3] = False
        move.move = chr(96 + origin % 10) + str(10 - origin // 10) + chr(96 + target % 10) + str(10 - target // 10)
        if promotion is not None:
            move.move += symbols[promotion]
        return move
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1963385) Verfasst am: 09.11.2014, 10:49    Titel: Antworten mit Zitat

Jetzt fehlt uns nicht mehr viel, um unser Schachprogramm fertig zu stellen.

Naive Idee: Wir lassen das Programm für die aktuelle Stellung alle Züge generieren, mit evaluate bewerten und den besten Zug spielen.

Bessere Idee: Wir lassen das Programm nicht nur alle Züge, sondern auch deren Folgezüge generieren. Wir suchen also zwei Halbzüge tief. Den zweiten Halbzug bewerten wir mit evaluate aber den Ersten bewerten wie wie den besten Folgezug (weil wir annehmen, dass der Spieler am Zug den besten Folgezug wählen wird). Und diese Vorgehensweise funktioniert auch bei noch größerer Suchtiefe.

Dafür brauchen wir zwei Funktionen, die sich gegenseitig rekursiv aufrufen, maxi und mini.

Code:

def maxi(position, depth, toplevel=True):
    if depth == 0:
        return position.evaluate()
    moves = position.generate_moves()
    if len(moves) == 0:
        if position.king_in_check(1):
            return -20000 # Schachmatt
        else:
            return 0 # Patt
    score = -100000
    for move in moves:
        move_score = mini(move, depth - 1, False)
        if toplevel:
            print(move.move + ': ' + str(move_score))
        if move_score > score:
            score = move_score
            if toplevel:
                global best_move
                best_move = move
    return score


def mini(position, depth, toplevel=True):
    if depth == 0:
        return position.evaluate()
    moves = position.generate_moves()
    if len(moves) == 0:
        if position.king_in_check(-1):
            return 20000 # Schachmatt
        else:
            return 0 # Patt
    score = 100000
    for move in moves:
        move_score = maxi(move, depth - 1, False)
        if toplevel:
            print(move.move + ': ' + str(move_score))
        if move_score < score:
            score = move_score
            if toplevel:
                global best_move
                best_move = move
    return score


- Stellungsbewertungen sind immer aus der Sicht von Weiß, d.h. Weiß versucht die Bewertung zu maximieren und Schwarz versucht sie zu minimieren. In Stellungen, wo Weiß am Zug ist, rufen wir deshalb maxi auf und wo Schwarz am Zug ist, mini.
- Bei jedem rekursiven Aufruf wird depth um eins kleiner, bis wir 0 erreicht haben. Dann geben wir das Ergebnis von evaluate zurück. Ansonsten geben wir immer die Bewertung des besten Folgezugs zurück.
- Wir behandeln hier auch den Sonderfall, dass eine Stellung keine Folgezüge hat. Dann ist die Partie entweder Matt oder Patt, je nachdem ob der König des Spielers am Zug im Schach steht.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1963387) Verfasst am: 09.11.2014, 10:54    Titel: Antworten mit Zitat

Jetzt fehlt nicht mehr viel Code:

Code:

def game_over(position, history):
    moves = position.generate_moves()
    if len(moves) == 0:
        if position.king_in_check(position.player):
            print('checkmate')
        else:
            print('stalemate')
        return True
    if position.checkmate_impossible():
        print('checkmate impossible')
        return True
    repetitions = 1
    for earlier_position in history:
        if earlier_position == position:
            repetitions += 1
            if repetitions == 3:
                print('threefold repetition')
                return True
    if position.halfmove_clock >= 100:
        print('draw by fifty-move rule')
        return True
    return False


if __name__ == '__main__':
    position = Position()
    history = []
    print(position)
    move_count = 1
    while True:
        # weiß (Mensch)
        legal = False
        while not legal:
            human_move = input(str(move_count) + '. ')
            moves = position.generate_moves()
            for move in moves:
                if move.move == human_move:
                    history.append(position)
                    position = move
                    legal = True
                    break
        print()
        print(position)
        if game_over(position, history):
            break
        # schwarz (Computer)
        mini(position, 4)
        print()
        print(str(move_count) + '. ...' + best_move.move)
        print()
        history.append(position)
        position = best_move
        print(position)
        if game_over(position, history):
            break
        move_count += 1


Beispielausgabe:

Code:

8 t s l d k l s t
7 b b b b b b b b
6 . . . . . . . .
5 . . . . . . . .
4 . . . . . . . .
3 . . . . . . . .
2 B B B B B B B B
1 T S L D K L S T
  a b c d e f g h

1. f2f3

8 t s l d k l s t
7 b b b b b b b b
6 . . . . . . . .
5 . . . . . . . .
4 . . . . . . . .
3 . . . . . B . .
2 B B B B B . B B
1 T S L D K L S T
  a b c d e f g h

b8a6: 40
b8c6: 0
g8f6: 0
g8h6: 40
a7a6: 65
a7a5: 25
b7b6: 35
b7b5: 65
c7c6: 40
c7c5: 30
d7d6: 20
d7d5: 0
e7e6: 0
e7e5: -20
f7f6: 40
f7f5: 40
g7g6: 35
g7g5: 105
h7h6: 65
h7h5: 25

1. ...e7e5

8 t s l d k l s t
7 b b b b . b b b
6 . . . . . . . .
5 . . . . b . . .
4 . . . . . . . .
3 . . . . . B . .
2 B B B B B . B B
1 T S L D K L S T
  a b c d e f g h

2. g2g4

8 t s l d k l s t
7 b b b b . b b b
6 . . . . . . . .
5 . . . . b . . .
4 . . . . . . B .
3 . . . . . B . .
2 B B B B B . . B
1 T S L D K L S T
  a b c d e f g h

b8a6: -30
b8c6: -50
d8e7: -30
d8f6: -35
d8g5: -30
d8h4: -20000
e8e7: -10
f8e7: -30
f8d6: -50
f8c5: -50
f8b4: -45
f8a3: -30
g8e7: -20
g8f6: -45
g8h6: -30
a7a6: -30
a7a5: -25
b7b6: -15
b7b5: -20
c7c6: -10
c7c5: -20
d7d6: -50
d7d5: -70
f7f6: -5
f7f5: -20
g7g6: -15
g7g5: 15
h7h6: -30
h7h5: -25
e5e4: -25

2. ...d8h4

8 t s l . k l s t
7 b b b b . b b b
6 . . . . . . . .
5 . . . . b . . .
4 . . . . . . B d
3 . . . . . B . .
2 B B B B B . . B
1 T S L D K L S T
  a b c d e f g h

checkmate
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1963388) Verfasst am: 09.11.2014, 11:01    Titel: Antworten mit Zitat

Wir haben nur ein paar kleine Regelabweichungen: Dreifache Wiederholung und die 50-Züge-Regel sind automatisch Remis und es wird nur eine Teilmenge der Stellungen erkannt, in denen Matt unmöglich ist.

Ansonsten spielt dieses Programm schon richtiges Schach. Alles was jetzt noch zu tun bleibt, sind Verbesserungen, z.B. Alpha-Beta-Pruning. Ich weiß aber noch nicht, wann ich mal Zeit und Lust aufbringe, um ein paar Verbesserungsmöglichkeiten zu erklären.
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
alae
auf eigenen Wunsch deaktiviert



Anmeldungsdatum: 23.03.2006
Beiträge: 7039

Beitrag(#1963389) Verfasst am: 09.11.2014, 11:02    Titel: Antworten mit Zitat

Hier noch mal der komplette Code:

Code:

symbols = {
    6: 'K', 5: 'D', 4: 'L', 3: 'S', 2: 'T', 1: 'B',
    -6: 'k', -5: 'd', -4: 'l', -3: 's', -2: 't', -1: 'b',
    0: '.'
}


long_index = (
    21, 22, 23, 24, 25, 26, 27, 28,
    31, 32, 33, 34, 35, 36, 37, 38,
    41, 42, 43, 44, 45, 46, 47, 48,
    51, 52, 53, 54, 55, 56, 57, 58,
    61, 62, 63, 64, 65, 66, 67, 68,
    71, 72, 73, 74, 75, 76, 77, 78,
    81, 82, 83, 84, 85, 86, 87, 88,
    91, 92, 93, 94, 95, 96, 97, 98,
)


white_king_table = (
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -20, -30, -30, -40, -40, -30, -30, -20,
    -10, -20, -20, -20, -20, -20, -20, -10,
     20,  20,   0,   0,   0,   0,  20,  20,
     20,  30,  10,   0,   0,  10,  30,  20,
)


white_king_endgame_table = (
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -20, -30, -30, -40, -40, -30, -30, -20,
    -10, -20, -20, -20, -20, -20, -20, -10,
     20,  20,   0,   0,   0,   0,  20,  20,
     20,  30,  10,   0,   0,  10,  30,  20,
)


white_queen_table = (
    -20, -10, -10,  -5,  -5, -10, -10, -20,
    -10,   0,   0,   0,   0,   0,   0, -10,
    -10,   0,   5,   5,   5,   5,   0, -10,
     -5,   0,   5,   5,   5,   5,   0,  -5,
      0,   0,   5,   5,   5,   5,   0,  -5,
    -10,   5,   5,   5,   5,   5,   0, -10,
    -10,   0,   5,   0,   0,   0,   0, -10,
    -20, -10, -10,  -5,  -5, -10, -10, -20,
)


white_bishop_table = (
    -20, -10, -10, -10, -10, -10, -10, -20,
    -10,   0,   0,   0,   0,   0,   0, -10,
    -10,   0,   5,  10,  10,   5,   0, -10,
    -10,   5,   5,  10,  10,   5,   5, -10,
    -10,   0,  10,  10,  10,  10,   0, -10,
    -10,  10,  10,  10,  10,  10,  10, -10,
    -10,   5,   0,   0,   0,   0,   5, -10,
    -20, -10, -10, -10, -10, -10, -10, -20,
)


white_knight_table = (
    -20, -10, -10, -10, -10, -10, -10, -20,
    -10,   0,   0,   0,   0,   0,   0, -10,
    -10,   0,   5,  10,  10,   5,   0, -10,
    -10,   5,   5,  10,  10,   5,   5, -10,
    -10,   0,  10,  10,  10,  10,   0, -10,
    -10,  10,  10,  10,  10,  10,  10, -10,
    -10,   5,   0,   0,   0,   0,   5, -10,
    -20, -10, -10, -10, -10, -10, -10, -20,
)


white_rook_table = (
      0,   0,   0,   0,   0,   0,   0,   0,
      5,  10,  10,  10,  10,  10,  10,   5,
     -5,   0,   0,   0,   0,   0,   0,  -5,
     -5,   0,   0,   0,   0,   0,   0,  -5,
     -5,   0,   0,   0,   0,   0,   0,  -5,
     -5,   0,   0,   0,   0,   0,   0,  -5,
     -5,   0,   0,   0,   0,   0,   0,  -5,
      0,   0,   0,   5,   5,   0,   0,   0,
)


white_pawn_table = (
      0,   0,   0,   0,   0,   0,   0,   0,
     50,  50,  50,  50,  50,  50,  50,  50,
     10,  10,  20,  30,  30,  20,  10,  10,
      5,   5,  10,  25,  25,  10,   5,   5,
      0,   0,   0,  20,  20,   0,   0,   0,
      5,  -5, -10,   0,   0, -10,  -5,   5,
      5,  10,  10, -20, -20,  10,  10,   5,
      0,   0,   0,   0,   0,   0,   0,   0,
)


black_king_table = (
     20,  30,  10,   0,   0,  10,  30,  20,
     20,  20,   0,   0,   0,   0,  20,  20,
    -10, -20, -20, -20, -20, -20, -20, -10,
    -20, -30, -30, -40, -40, -30, -30, -20,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
)


black_king_endgame_table = (
     20,  30,  10,   0,   0,  10,  30,  20,
     20,  20,   0,   0,   0,   0,  20,  20,
    -10, -20, -20, -20, -20, -20, -20, -10,
    -20, -30, -30, -40, -40, -30, -30, -20,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
    -30, -40, -40, -50, -50, -40, -40, -30,
)


black_queen_table = (
    -20, -10, -10,  -5,  -5, -10, -10, -20,
    -10,   0,   5,   0,   0,   0,   0, -10,
    -10,   5,   5,   5,   5,   5,   0, -10,
      0,   0,   5,   5,   5,   5,   0,  -5,
     -5,   0,   5,   5,   5,   5,   0,  -5,
    -10,   0,   5,   5,   5,   5,   0, -10,
    -10,   0,   0,   0,   0,   0,   0, -10,
    -20, -10, -10,  -5,  -5, -10, -10, -20,
)


black_bishop_table = (
    -20, -10, -10, -10, -10, -10, -10, -20,
    -10,   5,   0,   0,   0,   0,   5, -10,
    -10,  10,  10,  10,  10,  10,  10, -10,
    -10,   0,  10,  10,  10,  10,   0, -10,
    -10,   5,   5,  10,  10,   5,   5, -10,
    -10,   0,   5,  10,  10,   5,   0, -10,
    -10,   0,   0,   0,   0,   0,   0, -10,
    -20, -10, -10, -10, -10, -10, -10, -20,
)


black_knight_table = (
    -20, -10, -10, -10, -10, -10, -10, -20,
    -10,   5,   0,   0,   0,   0,   5, -10,
    -10,  10,  10,  10,  10,  10,  10, -10,
    -10,   0,  10,  10,  10,  10,   0, -10,
    -10,   5,   5,  10,  10,   5,   5, -10,
    -10,   0,   5,  10,  10,   5,   0, -10,
    -10,   0,   0,   0,   0,   0,   0, -10,
    -20, -10, -10, -10, -10, -10, -10, -20,
)


black_rook_table = (
      0,   0,   0,   5,   5,   0,   0,   0,
     -5,   0,   0,   0,   0,   0,   0,  -5,
     -5,   0,   0,   0,   0,   0,   0,  -5,
     -5,   0,   0,   0,   0,   0,   0,  -5,
     -5,   0,   0,   0,   0,   0,   0,  -5,
     -5,   0,   0,   0,   0,   0,   0,  -5,
      5,  10,  10,  10,  10,  10,  10,   5,
      0,   0,   0,   0,   0,   0,   0,   0,
)


black_pawn_table = (
      0,   0,   0,   0,   0,   0,   0,   0,
      5,  10,  10, -20, -20,  10,  10,   5,
      5,  -5, -10,   0,   0, -10,  -5,   5,
      0,   0,   0,  20,  20,   0,   0,   0,
      5,   5,  10,  25,  25,  10,   5,   5,
     10,  10,  20,  30,  30,  20,  10,  10,
     50,  50,  50,  50,  50,  50,  50,  50,
      0,   0,   0,   0,   0,   0,   0,   0,
)


king_offsets = (-11, -10, -9, -1, 1, 9, 10, 11)
queen_offsets = (-11, -10, -9, -1, 1, 9, 10, 11)
bishop_offsets = (-11, -9, 9, 11)
knight_offsets = (-21, -19, -12, -8, 8, 12, 19, 21)
rook_offsets = (-10, -1, 1, 10)
white_pawn_offsets = (-11, -9)
black_pawn_offsets = (9, 11)


class Position:
    def __init__(self, position=None):
        if position is not None:
            self.board = list(position.board)
            self.player = -position.player
            self.castling = list(position.castling)
            self.en_passant = None
            self.halfmove_clock = position.halfmove_clock
            self.move = None
        else:
            self.board = [
                -7, -7, -7, -7, -7, -7, -7, -7, -7, -7,
                -7, -7, -7, -7, -7, -7, -7, -7, -7, -7,
                -7, -2, -3, -4, -5, -6, -4, -3, -2, -7,
                -7, -1, -1, -1, -1, -1, -1, -1, -1, -7,
                -7,  0,  0,  0,  0,  0,  0,  0,  0, -7,
                -7,  0,  0,  0,  0,  0,  0,  0,  0, -7,
                -7,  0,  0,  0,  0,  0,  0,  0,  0, -7,
                -7,  0,  0,  0,  0,  0,  0,  0,  0, -7,
                -7,  1,  1,  1,  1,  1,  1,  1,  1, -7,
                -7,  2,  3,  4,  5,  6,  4,  3,  2, -7,
                -7, -7, -7, -7, -7, -7, -7, -7, -7, -7,
                -7, -7, -7, -7, -7, -7, -7, -7, -7, -7,
            ]
            self.player = 1
            self.castling = [True, True, True, True]
            self.en_passant = None
            self.halfmove_clock = 0
            self.move = None
   
    def __eq__(self, other):
        if self.board != other.board:
            return False
        if self.player != other.player:
            return False
        if self.castling != other.castling:
            return False
        if self.en_passant != other.en_passant:
            return False
        return True
   
    def __str__(self):
        s = ''
        for i in range(64):
            if i % 8 == 0:
                s += str(8 - i // 8) + ' '
            s += symbols[self.board[long_index[i]]] + ' '
            if i % 8 == 7:
                s += '\n'
        s += '  a b c d e f g h \n'
        return s
   
    def attacked(self, target, attacker):
        # Königsangriffe
        for offset in king_offsets:
            origin = target - offset
            if attacker * self.board[origin] == 6:
                return True
        # Damenangriffe
        for offset in queen_offsets:
            origin = target - offset
            while True:
                if attacker * self.board[origin] == 5:
                    return True
                elif self.board[origin] != 0:
                    break
                else:
                    origin -= offset
        # Läuferangriffe
        for offset in bishop_offsets:
            origin = target - offset
            while True:
                if attacker * self.board[origin] == 4:
                    return True
                elif self.board[origin] != 0:
                    break
                else:
                    origin -= offset
        # Springerangriffe
        for offset in knight_offsets:
            origin = target - offset
            if attacker * self.board[origin] == 3:
                return True
        # Turmangriffe
        for offset in rook_offsets:
            origin = target - offset
            while True:
                if attacker * self.board[origin] == 2:
                    return True
                elif self.board[origin] != 0:
                    break
                else:
                    origin -= offset
        # weiße Bauernangriffe
        if attacker == 1:
            for offset in white_pawn_offsets:
                origin = target - offset
                if self.board[origin] == 1:
                    return True
        # schwarze Bauernangriffe
        else:
            for offset in black_pawn_offsets:
                origin = target - offset
                if self.board[origin] == -1:
                    return True
        return False
   
    def capturable(self, square):
        i = self.player * self.board[square]
        return i <= -1 and i >= -6
   
    def capturable_or_empty(self, square):
        i = self.player * self.board[square]
        return i <= 0 and i >= -6
   
    def checkmate_impossible(self):
        knights = 0
        light_square_bishops = 0
        dark_square_bishops = 0
        for i in range(64):
            square = long_index[i]
            if abs(self.board[square]) in (5, 2, 1):
                return False
            elif abs(self.board[square]) == 4:
                if i % 2 == i // 8 % 2:
                    light_square_bishops += 1
                else:
                    dark_square_bishops += 1
            elif abs(self.board[square]) == 3:
                knights += 1
                if knights > 1:
                    return False
        if knights == 1 and light_square_bishops + black_square_bishops == 0:
            return True
        elif knights == 0 and (light_square_bishops == 0 or black_square_bishops == 0):
            return True
        else:
            return False
   
    def empty(self, square):
        return self.board[square] == 0
   
    def endgame(self):
        white_queens = 0
        white_rooks = 0
        white_minor_pieces = 0
        black_queens = 0
        black_rooks = 0
        black_minor_pieces = 0
        for i in range(64):
            piece = self.board[long_index[i]]
            if piece == 5:
                white_queens += 1
            elif piece == 2:
                white_rooks += 1
            elif piece in (3, 4):
                white_minor_pieces += 1
            elif piece == -5:
                black_queens += 1
            elif piece == -2:
                black_rooks += 1
            elif piece in (-3, -4):
                black_minor_pieces += 1
        white_endgame = white_queens == 0 or white_queens == 1 and white_rooks == 0 and white_minor_pieces <= 1
        black_endgame = black_queens == 0 or black_queens == 1 and black_rooks == 0 and black_minor_pieces <= 1
        return white_endgame and black_endgame
   
    def evaluate(self):
        score = 0
        endgame = self.endgame()
        for i in range(64):
            piece = self.board[long_index[i]]
            if piece == 6:
                if endgame:
                    score += white_king_endgame_table[i]
                else:
                    score += white_king_table[i]
            elif piece == 5:
                score += 900 + white_queen_table[i]
            elif piece == 4:
                score += 330 + white_bishop_table[i]
            elif piece == 3:
                score += 320 + white_knight_table[i]
            elif piece == 2:
                score += 500 + white_rook_table[i]
            elif piece == 1:
                score += 100 + white_pawn_table[i]
            elif piece == -6:
                if endgame:
                    score -= black_king_endgame_table[i]
                else:
                    score -= black_king_table[i]
            elif piece == -5:
                score -= 900 + black_queen_table[i]
            elif piece == -4:
                score -= 330 + black_bishop_table[i]
            elif piece == -3:
                score -= 320 + black_knight_table[i]
            elif piece == -2:
                score -= 500 + black_rook_table[i]
            elif piece == -1:
                score -= 100 + black_pawn_table[i]
        return score
   
    def generate_moves(self):
        moves = []
        for i in range(64):
            origin = long_index[i]
            if self.movable(origin):
                # König
                if abs(self.board[origin]) == 6:
                    for offset in king_offsets:
                        target = origin + offset
                        if self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                # Dame
                elif abs(self.board[origin]) == 5:
                    for offset in queen_offsets:
                        target = origin + offset
                        while self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if self.capturable(target):
                                break
                            target += offset
                # Läufer
                elif abs(self.board[origin]) == 4:
                    for offset in bishop_offsets:
                        target = origin + offset
                        while self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if self.capturable(target):
                                break
                            target += offset
                # Springer
                elif abs(self.board[origin]) == 3:
                    for offset in knight_offsets:
                        target = origin + offset
                        if self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                # Turm
                elif abs(self.board[origin]) == 2:
                    for offset in rook_offsets:
                        target = origin + offset
                        while self.capturable_or_empty(target):
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if self.capturable(target):
                                break
                            target += offset
                # weißer Bauer
                elif self.board[origin] == 1:
                    target = origin - 10
                    if self.empty(target):
                        if target // 10 == 2:
                            for promotion in (5, 4, 3, 2):
                                move = self.make_move(origin, target, promotion)
                                if move.legal():
                                    moves.append(move)
                        else:
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if origin // 10 == 8:
                                target -= 10
                                if self.empty(target):
                                    move = self.make_move(origin, target)
                                    move.en_passant = target
                                    if move.legal():
                                        moves.append(move)
                    for offset in white_pawn_offsets:
                        target = origin + offset
                        if self.capturable(target):
                            if target // 10 == 2:
                                for promotion in (5, 4, 3, 2):
                                    move = self.make_move(origin, target, promotion)
                                    move.en_passant = target
                                    if move.legal():
                                        moves.append(move)
                            else:
                                move = self.make_move(origin, target)
                                if move.legal():
                                    moves.append(move)
                # schwarzer Bauer
                elif self.board[origin] == -1:
                    target = origin + 10
                    if self.empty(target):
                        if target // 10 == 9:
                            for promotion in (-5, -4, -3, -2):
                                move = self.make_move(origin, target, promotion)
                                if move.legal():
                                    moves.append(move)
                        else:
                            move = self.make_move(origin, target)
                            if move.legal():
                                moves.append(move)
                            if origin // 10 == 3:
                                target += 10
                                if self.empty(target):
                                    move = self.make_move(origin, target)
                                    move.en_passant = target
                                    if move.legal():
                                        moves.append(move)
                    for offset in black_pawn_offsets:
                        target = origin + offset
                        if self.capturable(target):
                            if target // 10 == 9:
                                for promotion in (-5, -4, -3, -2):
                                    move = self.make_move(origin, target, promotion)
                                    if move.legal():
                                        moves.append(move)
                            else:
                                move = self.make_move(origin, target)
                                if move.legal():
                                    moves.append(move)
        # Rochade
        if self.player == 1:
            if self.castling[0] and self.board[96] == 0 and self.board[97] == 0 and not self.attacked(95, -1) and not self.attacked(96, -1) and not self.attacked(97, -1):
                move = self.make_move(95, 97)
                move.board[96] = 2
                move.board[98] = 0
                if move.legal():
                    moves.append(move)
            if self.castling[1] and self.board[94] == 0 and self.board[93] == 0 and self.board[92] == 0 and not self.attacked(95, -1) and not self.attacked(94, -1) and not self.attacked(93, -1):
                move = self.make_move(95, 93)
                move.board[94] = 2
                move.board[91] = 0
                if move.legal():
                    moves.append(move)
        else:
            if self.castling[2] and self.board[26] == 0 and self.board[27] == 0 and not self.attacked(25, 1) and not self.attacked(26, 1) and not self.attacked(27, 1):
                move = self.make_move(25, 27)
                move.board[26] = -2
                move.board[28] = 0
                if move.legal():
                    moves.append(move)
            if self.castling[3] and self.board[24] == 0 and self.board[23] == 0 and self.board[22] == 0 and not self.attacked(25, 1) and not self.attacked(24, 1) and not self.attacked(23, 1):
                move = self.make_move(25, 23)
                move.board[24] = -2
                move.board[21] = 0
                if move.legal():
                    moves.append(move)
        # en passant
        if self.en_passant is not None:
            if self.player == 1:
                target = self.en_passant - 10
                origin = self.en_passant - 1
                if self.board[origin] == 1:
                    move = self.make_move(origin, target)
                    move.board[self.en_passant] = 0
                    if move.legal():
                        moves.append(move)
                origin = self.en_passant + 1
                if self.board[origin] == 1:
                    move = self.make_move(origin, target)
                    move.board[self.en_passant] = 0
                    if move.legal():
                        moves.append(move)
            else:
                target = self.en_passant + 10
                origin = self.en_passant - 1
                if self.board[origin] == -1:
                    move = self.make_move(origin, target)
                    move.board[self.en_passant] = 0
                    if move.legal():
                        moves.append(move)
                origin = self.en_passant + 1
                if self.board[origin] == -1:
                    move = self.make_move(origin, target)
                    move.board[self.en_passant] = 0
                    if move.legal():
                        moves.append(move)
        return moves
   
    def king_in_check(self, defender):
        for i in range(64):
            target = long_index[i]
            if defender * self.board[target] == 6:
                return self.attacked(target, -defender)
   
    def legal(self):
        return not self.king_in_check(-self.player)
   
    def make_move(self, origin, target, promotion=None):
        move = Position(self)
        if move.board[origin] == 1 or move.board[origin] == -1 or move.board[target] != 0:
            move.halfmove_clock = 0
        else:
            move.halfmove_clock += 1
        if promotion is not None:
            move.board[target] = promotion
        else:
            move.board[target] = move.board[origin]
        move.board[origin] = 0
        if origin == 95 or origin == 98 or target == 98:
            move.castling[0] = False
        if origin == 95 or origin == 91 or target == 91:
            move.castling[1] = False
        if origin == 25 or origin == 28 or target == 28:
            move.castling[2] = False
        if origin == 25 or origin == 21 or target == 21:
            move.castling[3] = False
        move.move = chr(96 + origin % 10) + str(10 - origin // 10) + chr(96 + target % 10) + str(10 - target // 10)
        if promotion is not None:
            move.move += symbols[promotion]
        return move
   
    def movable(self, square):
        i = self.player * self.board[square]
        return i >= 1 and i <= 6


def maxi(position, depth, toplevel=True):
    if depth == 0:
        return position.evaluate()
    moves = position.generate_moves()
    if len(moves) == 0:
        if position.king_in_check(1):
            return -20000 # Schachmatt
        else:
            return 0 # Patt
    score = -100000
    for move in moves:
        move_score = mini(move, depth - 1, False)
        if toplevel:
            print(move.move + ': ' + str(move_score))
        if move_score > score:
            score = move_score
            if toplevel:
                global best_move
                best_move = move
    return score


def mini(position, depth, toplevel=True):
    if depth == 0:
        return position.evaluate()
    moves = position.generate_moves()
    if len(moves) == 0:
        if position.king_in_check(-1):
            return 20000 # Schachmatt
        else:
            return 0 # Patt
    score = 100000
    for move in moves:
        move_score = maxi(move, depth - 1, False)
        if toplevel:
            print(move.move + ': ' + str(move_score))
        if move_score < score:
            score = move_score
            if toplevel:
                global best_move
                best_move = move
    return score


def game_over(position, history):
    moves = position.generate_moves()
    if len(moves) == 0:
        if position.king_in_check(position.player):
            print('checkmate')
        else:
            print('stalemate')
        return True
    if position.checkmate_impossible():
        print('checkmate impossible')
        return True
    repetitions = 1
    for earlier_position in history:
        if earlier_position == position:
            repetitions += 1
            if repetitions == 3:
                print('threefold repetition')
                return True
    if position.halfmove_clock >= 100:
        print('draw by fifty-move rule')
        return True
    return False


if __name__ == '__main__':
    position = Position()
    history = []
    print(position)
    move_count = 1
    while True:
        # weiß (Mensch)
        legal = False
        while not legal:
            human_move = input(str(move_count) + '. ')
            moves = position.generate_moves()
            for move in moves:
                if move.move == human_move:
                    history.append(position)
                    position = move
                    legal = True
                    break
        print()
        print(position)
        if game_over(position, history):
            break
        # schwarz (Computer)
        mini(position, 4)
        print()
        print(str(move_count) + '. ...' + best_move.move)
        print()
        history.append(position)
        position = best_move
        print(position)
        if game_over(position, history):
            break
        move_count += 1
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
Beiträge der letzten Zeit anzeigen:   
Neues Thema eröffnen   Neue Antwort erstellen   Drucker freundliche Ansicht    Freigeisterhaus Foren-Übersicht -> DAU's Paradise Alle Zeiten sind GMT + 1 Stunde
Gehe zu Seite Zurück  1, 2
Seite 2 von 2

 
Gehe zu:  
Du kannst keine Beiträge in dieses Forum schreiben.
Du kannst auf Beiträge in diesem Forum nicht antworten.
Du kannst deine Beiträge in diesem Forum nicht bearbeiten.
Du kannst deine Beiträge in diesem Forum nicht löschen.
Du kannst an Umfragen in diesem Forum nicht mitmachen.



Impressum & Datenschutz


Powered by phpBB © 2001, 2005 phpBB Group