niklasf / python-chess

A chess library for Python, with move generation and validation, PGN parsing and writing, Polyglot opening book reading, Gaviota tablebase probing, Syzygy tablebase probing, and UCI/XBoard engine communication
https://python-chess.readthedocs.io/en/latest/
GNU General Public License v3.0
2.43k stars 531 forks source link

can eat your own pieces and play with only one side #592

Closed OfekShochat closed 4 years ago

OfekShochat commented 4 years ago

I am making a chess engine for the experience (that is why I am using python). when I write the board.legal_moves it is giving me an option to eat my pieces. So the engine is eating itself. Secondly, you can just play a game with only one side playing. All of the above can be helpful sometimes when you want to get to a specific board. So can you make a settings function or something like that? Is it only a problem I have?

EDIT: can eat king too and the game doesn't stop even if I say

if board.is_game_over(): break

and the king stepping into checks too

niklasf commented 4 years ago

board.legal_moves should not do that. Please show a concrete code example that behaves incorrectly.

OfekShochat commented 4 years ago

Hi, I think the thing that is behaving incorrectly is only the board.legal_moves Because it is the only thing that can allow us to capture something that he is not supposed to and make moves that it is not supposed to. And thank you so much for the prompt reply. I expected it to be at least 20 minutes.

niklasf commented 4 years ago

So far I wasn't able to find an example of that. Please give a concrete position where it happens.

OfekShochat commented 4 years ago
r n b q k b n r
p p p p . p p p
. . . . P . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
P P P P P P P P
R N B Q K B N R

and then:

r n b q k b n r
p p p . . p p p
. . . . P . . .
. . . p . . . .
. . . . . . . .
. . . . . . . .
P P P P P P P P
R N B Q K B N R

and:

r n b q k b n r p p p p . p p p . . . . P . . . . . . . . . . . . . . . . . . . . . . . . . . . P P P P P P P P R N B Q K B N R r n b q k b n r p p p . . p p p . . . . P . . . . . . p . . . . . . . . . . . . . . . . . . . . P P P P P P P P R N B Q K B N R r n b q k b n r p p p . . p p p . . . . P . . . . . . p . . . . . . . . . . . . . . . . . . . . P P P P P P P P R N B Q K B N R r n b q k b n r p p p . . p p p . . . . P . . . . . . . . . . . . . . P . . . . . . . . . . . . P P P P P P P P R N B Q K B N R r n b q k b n r p p p . . p p p . . . . P . . . . . . . . . . . . . . P . . . . . . . . . . . . P P P P P P P P R N B Q K B N R r n b . k b n r p p p . . p p p . . . . P . . . . . . q . . . . . . . P . . . . . . . . . . . . P P P P P P P P R N B Q K B N R r n b . k b n r p p p . . p p p . . . . P . . . . . . q . . . . . . . P . . . . . . . . . . . . P P P P P P P P R N B Q K B N R r n b . k b n r p p p . . p p p . . . . P . . . . . . q . . . . . . . P P . . . . . . . . . . . P P P P . P P P R N B Q K B N R r n b . k b n r p p p . . p p p . . . . P . . . . . . q . . . . . . . P P . . . . . . . . . . . P P P P . P P P R N B Q K B N R

this is one example for the second issue I mentioned

niklasf commented 4 years ago

Already the first position

r n b q k b n r
p p p p . p p p
. . . . P . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
P P P P P P P P
R N B Q K B N R

looks wierd with the white pawn on e6. How was it produced?

OfekShochat commented 4 years ago

this is how it was played. I didn't leave out any position

niklasf commented 4 years ago

I am asking for the code that produced this sequence, even just the first position.

OfekShochat commented 4 years ago

with the algorithm?

OfekShochat commented 4 years ago

this is the full code:

from math import inf
import chess
from time import time
import io
import table_analysis
import threading
from time import sleep
import sys
import os

def times_up(st, how_much):
   if time() - st >= how_much:
      return True
   return False

def get_piece_val(piece:str):
   temp = 0
   if piece == "k":
      temp = -100000
   if piece == "q":
      temp = -900
   if piece == "r":
      temp = -500
   if piece == "b":
      temp = -400
   if piece == "n":
      temp = -300
   if piece == "p":
      temp = -10
   if piece == "K":
      temp = 100000
   if piece == "Q":
      temp = 900
   if piece == "R":
      temp = 500
   if piece == "B":
      temp = 400
   if piece == "N":
      temp = 300
   if piece == "P":
      temp = 10
   return temp

def evaluate(board_fen, color, debugger=False):
   thisboard = chess.Board(board_fen)
   evaluation = 0
   for i in str(thisboard):
      evaluation += get_piece_val(i)
      #evaluation += table_analysis.tables[get_piece_val(i)]
   for i in chess.LegalMoveGenerator(thisboard): evaluation += 1 * table_analysis.weights["mobility"]
   if list(chess.LegalMoveGenerator(thisboard)) == []:
      if thisboard.is_check:
         return 10000
      return 0
   move = list(thisboard.legal_moves)[0]
   move = chess.Move.from_uci(str(move))
   thisboard.push(move)
   for i in chess.LegalMoveGenerator(thisboard): evaluation -= 1 * table_analysis.weights["mobility"]
   return evaluation * color

def iterative_deepening(max_depth, board, color):
   firstguess = 0
   st = time()
   previous_fen = board.fen()
   board = chess.Board(previous_fen)
   value = -10000
   for d in range(max_depth):
      for move in board.legal_moves:
         board.push(chess.Move.from_uci(str(move)))
         firstguess = negamax(board, d, color)
         board = chess.Board(previous_fen)
         if firstguess > value:
            best_move = move
            value = firstguess
         if times_up(st, 1) or firstguess >= 10000: 
            break
      if times_up(st, 1) or firstguess >= 10000: 
         break
   return str(best_move)
def negamax(board, depth, color: int):
   """
   color can be either 1 or -1. 1 for white and -1 for black
   """
   if depth == 0:
      return evaluate(board.fen(), color)
   value = -inf
   previous_fen = board.fen()
   for move in board.legal_moves:  
      board.push(chess.Move.from_uci(str(move)))
      nega = -negamax(board, depth-1, -color)
      value = max(value, nega) # can do class node that stores a board and the possible moves and then I can say if it is a terminal node (a node that determains loss, win and draws) with a parameter purpose.
      board = chess.Board(previous_fen)
   return value

def quiesce(alpha,beta, board):
   stand_pat = evaluate(board.fen(), 1)
   if( stand_pat >= beta ): return beta
   if( alpha < stand_pat ): alpha = stand_pat

   for capture in board.generate_pseudo_legal_captures():
      board.push(chess.Move.from_uci(str(capture)))
      score = -quiesce( -beta, -alpha, board )
      board.pop()
      if( score >= beta ): return beta
      if( score > alpha ): alpha = score

   return alpha

def main1(time_left):
   board = chess.Board()
   move_num = 0
   game = ""
   pgn = ""
   try:
      while not board.is_game_over():
         move_num += 1
         value = iterative_deepening(10, board, 1)
         board.push(chess.Move.from_uci(value))
         #os.system("clear")
         print(board)
         game += "%s. %s" % (str(move_num), str(value))
         if board.is_game_over(): break
         value = iterative_deepening(10, board, -1)
         #os.system("clear")
         print(board)
         game += " %s " % value
         pgn = io.StringIO(game)
   except Exception as err:
      print(err)
      for i in pgn:
         print(i)
      print(move_num)
   for i in pgn:
      print(i)
if __name__ == "__main__":
    main1(10)
niklasf commented 4 years ago

There's at least one bug in your code: In (C) a new position is assigned to board while still iterating over the legal moves of the old position (A).

def iterative_deepening(max_depth, board, color):
   firstguess = 0
   st = time()
   previous_fen = board.fen()
   board = chess.Board(previous_fen)
   value = -10000
   for d in range(max_depth):
      for move in board.legal_moves:  # (A)
         board.push(chess.Move.from_uci(str(move)))  # (B)
         firstguess = negamax(board, d, color)
         board = chess.Board(previous_fen)  # (C)
         if firstguess > value:
            best_move = move
            value = firstguess
         if times_up(st, 1) or firstguess >= 10000: 
            break
      if times_up(st, 1) or firstguess >= 10000: 
         break
   return str(best_move)

I recommend putting something like assert board.is_legal(move) before each board.push() to find these issues. Remove the blanket except Exception to get better backtraces.

Unrelated: I noticed a lot of useless string conversions: board.push(chess.Move.from_uci(str(move))) where move is a Move object can be written as board.push(move).

OfekShochat commented 4 years ago

tried to do board.push(move) but got an error somhow. I don't know why but board.pop() isn't working as well and thank you so much