Chess game in Python - follow-up

Three weeks ago I wrote the first version of my chess game in Python and shared on Code Review. Thanks to your suggestions, I improved my code. I would like to know if I am going in good direction with all this stuff.

__author__ = 'Goldsmitd'
class Condition:

def range(x,y):
return x > 8 and y > 8

def if_figure(board,x,y):
return board[x][y].sl == '.'

def same_team(x1,y1,x2,y2,board):
if board[x1][y1].team == board[x2][y2].team:
return True
else:
return False

def s_choice(board):
while True:
try:
print('give x and y')
x = int(input())
y = int(input())
except:
print('Coordinates can only be integers')
if Condition.range(x,y):
print('Coordinates are out of range')
elif Condition.if_figure(board,x,y):
print('Square chosen by you is empty')
else:
return x,y
break

def d_choice(board):
while True:
try:
print('give x and y')
x=int(input())
y=int(input())
except:
print('Coordinates can only be integers')
if Condition.range(x,y):
print('Coordinates are out of range')
else:
return x,y
break

def kill(x1,y1,x2,y2,board):
if board[x1][y1].team == 'white' and board[x2][y2].team == 'black':
return True
elif board[x1][y1].team == 'black' and board[x2][y2].team == 'white':
return True
else:
return False

def Pawnkill(x1,y1,x2,y2,board):
if board[x1][y1].team == 'white' and board[x2][y2].team == 'black' and board[x1][y1].name == 'Pawn':
return True
elif board[x1][y1].team == 'black' and board[x2][y2].team == 'white'and board[x1][y1].name == 'Pawn':
return True
else:
return False

def solid(x1,y1,x2,y2,board):
if board[x1][y1].name=='Rook':
if x2>x1:
for i in range(x1+1,x2):
if board[i][y1].sl != '.':
return False
break
elif x2<x1:
for i in range(x2+1,x1):
if board[i][y1].sl != '.':
return False
break
elif y2>y1:
for j in range(y1+1,y2):
if board[x1][j].sl != '.':
return False
break
elif y2<y1:
for j in range(y2+1,y1):
if board[x1][j].sl != '.':
return False
break
else:
return True

elif board[x1][y1].name=='Bishop':
if x2>x1 and y2>y1:
for i in range(x1+1,x2):
for j in range(y1+1,y2):
if board[i][j].sl != '.':
return False
break
elif x2<x1 and y2<y1:
for i in range(x2+1,x1):
for j in range(y2+1,y1):
if board[i][j].sl != '.':
return False
break
elif x2<x1 and y2>y1:
for i in range(x2+1,x1):
for j in range(y1+1,y2):
if board[i][j].sl != '.':
return False
break
elif x2>x1 and y2<y1:
for i in range(x1+1,x2):
for j in range(y2+1,y1):
if board[i][j].sl != '.':
return False
break
else:
return True

elif board[x1][y1].name=='Queen':
if x2>x1 and y2>y1:
for i in range(x1+1,x2):
for j in range(y1+1,y2):
if board[i][j].sl != '.':
return False
break
elif x2<x1 and y2<y1:
for i in range(x2+1,x1):
for j in range(y2+1,y1):
if board[i][j].sl != '.':
return False
break
elif x2<x1 and y2>y1:
for i in range(x2+1,x1):
for j in range(y1+1,y2):
if board[i][j].sl != '.':
return False
break
elif x2>x1 and y2<y1:
for i in range(x1+1,x2):
for j in range(y2+1,y1):
if board[i][j].sl != '.':
return False
break
elif x2>x1:
for i in range(x1+1,x2):
if board[i][y1].sl != '.':
return False
break
elif x2<x1:
for i in range(x2+1,x1):
if board[i][y1].sl != '.':
return False
break
elif y2>y1:
for j in range(y1+1,y2):
if board[x1][j].sl != '.':
return False
break
elif y2<y1:
for j in range(y2+1,y1):
if board[x1][j].sl != '.':
return False
break

else:
return True

else:
return True

class Number:
def __init__(self,sl):
self.sl=sl

class Empty:
def __init__(self,x,y,sl,team):
self.name = 'Empty'
self.x = x
self.y = y
self.sl = sl
self.team = team

class Rook:
def __init__(self,x,y,sl,team):
self.name = 'Rook'
self.x = x
self.y = y
self.sl = sl
self.team = team

def req(self,sx,sy,dx,dy,board):
if  ( dx==sx or dy==sy ) :
return True
else:
return False

class Knight:
def __init__(self,x,y,sl,team):
self.name = 'Knight'
self.x = x
self.y = y
self.sl = sl
self.team = team

def req(self,sx,sy,dx,dy,board):
if  (abs(dx - sx)**2+abs(dy - sy)**2 == 5) :
return True
else:
return False

class Bishop:
def __init__(self,x,y,sl,team):
self.name = 'Bishop'
self.x = x
self.y = y
self.sl = sl
self.team = team

def req(self,sx,sy,dx,dy,board):
if  (abs(dx - sx)==abs(dy - sy)) :
return True
else:
return False

class Queen:
def __init__(self,x,y,sl,team):
self.name = 'Queen'
self.x = x
self.y = y
self.sl = sl
self.team = team

def req(self,sx,sy,dx,dy,board):
if  (dx == sx or dy == sy or (abs(dx - sx) == abs(dy - sy))) :
return True
else:
return False

class King:
def __init__(self,x,y,sl,team):
self.name = 'King'
self.x = x
self.y = y
self.sl = sl
self.team = team

def req(self,sx,sy,dx,dy,board):
if  abs(dx-sx) < 2 and abs(dy-sy) <  2 :
return True
else:
return False

class Pawn:
def __init__(self,x,y,sl,team):
self.name = 'Pawn'
self.x = x
self.y = y
self.sl = sl
self.team = team
def req(self,sx,sy,dx,dy,board):
if board[sx][sy].team == "white" and dx-sx == -1:
return True
elif board[sx][sy].team == 'black' and dx-sx == 1:
return True
else:
return False

class Chess_Board:
def __init__(self):
self.board = [[Empty(x='',y='',sl='.',team='')]*9 for _ in range(9)]
self.board[0][0] = Rook(x=0,y=0,sl='r',team='black')
self.board[0][1] = Knight(x=0,y=1,sl='n',team='black')
self.board[0][2] = Bishop(x=0,y=2,sl='b',team='black')
self.board[0][3] = Queen(x=0,y=3,sl='q',team='black')
self.board[0][4] = King(x=0,y=4,sl='k',team='black')
self.board[0][5] = Bishop(x=0,y=5,sl='b',team='black')
self.board[0][6] = Knight(x=0,y=6,sl='n',team='black')
self.board[0][7] = Rook(x=0,y=7,sl='r',team='black')
self.board[1][0] = Pawn(x=1,y=0,sl='p',team='black')
self.board[1][1] = Pawn(x=1,y=1,sl='p',team='black')
self.board[1][2] = Pawn(x=1,y=2,sl='p',team='black')
self.board[1][3] = Pawn(x=1,y=3,sl='p',team='black')
self.board[1][4] = Pawn(x=1,y=4,sl='p',team='black')
self.board[1][5] = Pawn(x=1,y=5,sl='p',team='black')
self.board[1][6] = Pawn(x=1,y=6,sl='p',team='black')
self.board[1][7] = Pawn(x=1,y=7,sl='p',team='black')
self.board[7][0] = Rook(x=7,y=0,sl='R',team='white')
self.board[7][1] = Knight(x=7,y=1,sl='N',team='white')
self.board[7][2] = Bishop(x=7,y=2,sl='B',team='white')
self.board[7][3] = Queen(x=7,y=3,sl='Q',team='white')
self.board[7][4] = King(x=7,y=4,sl='K',team='white')
self.board[7][5] = Bishop(x=7,y=5,sl='B',team='white')
self.board[7][6] = Knight(x=7,y=6,sl='N',team='white')
self.board[7][7] = Rook(x=7,y=7,sl='R',team='white')
self.board[2][0] = Pawn(x=3,y=0,sl='P',team='white')
self.board[6][1] = Pawn(x=6,y=1,sl='P',team='white')
self.board[6][2] = Pawn(x=6,y=2,sl='P',team='white')
self.board[6][3] = Pawn(x=6,y=3,sl='P',team='white')
self.board[6][4] = Pawn(x=6,y=4,sl='P',team='white')
self.board[6][5] = Pawn(x=6,y=5,sl='P',team='white')
self.board[6][6] = Pawn(x=6,y=6,sl='P',team='white')
self.board[6][7] = Pawn(x=6,y=7,sl='P',team='white')
for i in range(9):
self.board[i][8 ]= Number(sl=i)
for j in range(9):
self.board[8][j] = Number(sl=j)

def display(self):
for i in range(9):
for j in range(9):
print (self.board[i][j].sl, end=' ')
print()

def move(self):
while True:
print('Give a position of figure')
sx,sy=Condition.s_choice(self.board)
print(self.board[sx][sy].name)
print('Now choose a destnation')
dx,dy=Condition.d_choice(self.board)
mark_same=Condition.same_team(sx,sy,dx,dy,self.board)
mark_kill=Condition.kill(sx,sy,dx,dy,self.board)
mark_Pawnkill=Condition.Pawnkill(sx,sy,dx,dy,self.board)
mark_solid=Condition.solid(sx,sy,dx,dy,self.board)
mark_move=self.board[sx][sy].req(sx,sy,dx,dy,self.board)
if mark_solid==False:
print('Figures are not ghosts')

elif (mark_Pawnkill == True and abs(dx-sx) == abs(dy-sy) and mark_same == False):
self.board[dx][dy] = self.board[sx][sy]
self.board[dx][dy].x = dx
self.board[dx][dy].y = dy
self.board[sx][sy] = Empty(x='',y='',sl='.',team='')
return self.board
break

elif (mark_move == True and mark_Pawnkill == False and (mark_kill == True or mark_same == False)):
self.board[dx][dy] = self.board[sx][sy]
self.board[dx][dy].x = dx
self.board[dx][dy].y = dy
self.board[sx][sy] = Empty(x='',y='',sl='.',team='')
return self.board
break
else:
print('Figure can not move here, try again')
continue

a=Chess_Board()
a.display()
#Early version of game engine
while True:
a.move()
a.display()


Very nice game. Good effort! A few suggestions to make it better.

The instruction to give x and y is a little confusing i tried entering 3, 6 then 3 and 6 ... it took me a while to figure out that I needed to enter one and then the other. making this small change could make it easier:

try:
print('give x and y')
x = int(input("x:"))
y = int(input("y:"))


Not really a "code problem" per se, but rather a chess problem. The numbering on the board should start from the bottom left corner and move out. And it should count from 1, not from 0.

r n b q k b n r 0
p p p p p p p p 1
P . . . . . . . 2
. . . . . . . . 3
. . . . . . . . 4
. . . . . . . . 5
. P P P P P P P 6
R N B Q K B N R 7
0 1 2 3 4 5 6 7 8


So that the white Rook on The Queen side is at coordinates (1,1) and the other white rook at coordinates (1,8)

The system accepts negative values for x and y, as well as the number 8... all of these generate an error (since the grid currently starts counting at 0)

At the start of the game, the left-most white pawn is sitting all the way across the board

 r n b q k b n r 0
p p p p p p p p 1
>P . . . . . . . 2
. . . . . . . . 3
. . . . . . . . 4
. . . . . . . . 5
. P P P P P P P 6
R N B Q K B N R 7
0 1 2 3 4 5 6 7 8


You could use unicode chess pieces, this would allow you to free the alphabet and use the traditional Letter-Number chess coordinates. This is the Wikipedia link. You can get them into python using chr()

for i in range(12):
chr(9812+i)


output:

'♔'
'♕'
'♖'
'♗'
'♘'
'♙'
'♚'
'♛'
'♜'
'♝'
'♞'
'♟'

• Condition is not a class, but a set of static functions, so you should define those functions outside a class, probably in a module (a file).

• Inconsistent function signatures: if_figure(board,x,y) and same_team(x1,y1,x2,y2,board). Board should be always first or last.

• Your classes Rook, Knight, etc. are nearly identical, so you could save a lot of code lines by using sub-classing.

• You duplicate the position information: self.board[0][0] = Rook(x=0,y=0,sl='r',team='black'). The position is both in the board and the Rook. You want to avoid information duplication since it adds a lot of complication because you have to make sure you update values everywhere at the same time. I think part of the problem here is that you did not use "separation of concerns": you should different (and separated) code for the game logic and for the GUI. You should not have the edge numbers "1", "2", on the same board as the board used for logic.

• Similarly, the possible motions are coded in both req and solid. Should be only coded in one place. BTW, solid should be renamed to something more informative.

• For the team instead of "white"/"black", I would just use a boolean.

• Chess_board.__init__ could use for-loops for the pawns.

• Chess_Board should be ChessBoard.

This, at least the O.P., is still flagged as an active question, but answers are somewhat obsolete, so I'm also throwing in my 2 cents:

First and foremost, there is now an "official"(?) chess module in Python, for which documentation is available at https://python-chess.readthedocs.io. So you can do

> pip install chess   # in the linux or windows shell
> python
>>> import chess
>>> B = chess.Board()
>>> B.push_san('e4')
>>> print(B)
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


Of course it's OK to develop your own module, insofar more as this module seems not particularly optimized for efficiency. (I think it is intended to serve as an interface rather than a basis for building a true chess program/engine.) But you'll surely find inspiration there. In particular:

• you might want to start implementing a Board.fen() method, then Board.__init__() can use this with the FEN of the standard starting position. (That init part is one of things that hurt my eyes most in your code ;-))
• you might also want to code the possible moves of the pieces in a more condensed way. Bitmaps might be good for efficiency. IIRC, when I wrote my first chess program back in the 80's I used a 10x10 bitmap with additional 0-th and 9-th rank & file which delimited the board for efficient move generation.

Some more code review:

• Your different pieces 'King', 'Queen', ... (maybe also 'Empty' - is it really useful?) could/should be implemented as a subclass of a class 'Piece'. (This should be a default reaction whenever you see duplicated code.)

• Similarly to the suggestion about Board.fen(), you should also implement methods like Board.parse_move() and/or Board.make_move() to "streamline" the input part; a class Move might be useful (contrary to the class Number).

• I'm not sure the user interface which asks for moves should be part of the Board. Or if it does, I'd suggest to structure it along these lines:

   def play_game(self):
while not self.game_over:
if move: self.make_move(); this.display()