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()
3 Answers 3
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. Chess 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
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)
andsame_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 theRook
. 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
andsolid
. 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 beChessBoard
.
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, thenBoard.__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 likeBoard.parse_move()
and/orBoard.make_move()
to "streamline" the input part; a classMove
might be useful (contrary to the classNumber
).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: move = self.ask_for_move(); if move: self.make_move(); this.display() def ask_for_move(self): choice = input("Enter a move in standard notation, or a command ('?' for help): ") if choice=='?': ... elif choice=='x': ... else: move = self.parse_move(choice, showMessage=True) # return None if not a legal move if move: self.make_move(move)
-
1\$\begingroup\$ Welcome to Code Review! Thanks for this great answer - I hope to see more from you in future! \$\endgroup\$Toby Speight– Toby Speight2021年12月15日 16:31:09 +00:00Commented Dec 15, 2021 at 16:31
-
\$\begingroup\$ Thank you, I'll do my best. Also noted your edit in formatting (indentation of code-fence when inside a list or similar), will try to remember. \$\endgroup\$Max– Max2021年12月18日 02:57:18 +00:00Commented Dec 18, 2021 at 2:57