7
\$\begingroup\$

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()
\$\endgroup\$

3 Answers 3

6
\$\begingroup\$

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:

'♔'
'♕'
'♖'
'♗'
'♘'
'♙'
'♚'
'♛'
'♜'
'♝'
'♞'
'♟'
\$\endgroup\$
2
\$\begingroup\$
  • 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.

\$\endgroup\$
2
\$\begingroup\$

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:
               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)
    
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Welcome to Code Review! Thanks for this great answer - I hope to see more from you in future! \$\endgroup\$ Commented 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
    Commented Dec 18, 2021 at 2:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.