HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Enumerating moves for a chess piece

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
movespieceenumeratingchessfor

Problem

I'm just at the beginning stages of making a simple chess engine. I have a function that when passed the x and y coordinates on a 2D list can enumerate all possible moves for a piece. It assumes there are no other pieces on the board, and that all moves are possible (see example list of moves below) and a separate function 'cleans' the list and removes any moves that aren't possible (say if the space is taken, or if the value would put the piece off of the board) My code works, but it is large and redundant, so I was wondering if anybody had any idea how to better accomplish this? Assuming the computer will always be playing black.

The list before the cleanup for a knight which has not moved at the beginning of the game would look like this:

[[[2], [2]], [[2], [0]], [[1], [3]], [[1], [3]], [[-2], [0]], [[-2], [2]], [[-1], [3]], [[-1], [-1]]]


and after clean up, looks like this:

[[[2], [2]], [[2], [0]]]


So it can go to A6 or C6 in chess terms

and here is the function:

```
def enumerate_moves(x, y):

potential_moves = [] # moves before clean up function

# ------------------------------------------------------------------------------
# resolve pawn moves. 4 possible moves maximally
if board[x][y] == "bPawn":

potential_moves.append([[x+1], [y]])

# if the pawn is in the second rank (has not moved)
try:
if board[x+2][y] == "" and x == 1 and board[x+1][y] == "":
potential_moves.append([[x+2], [y]])
except IndexError:
pass

try:
if board[x+1][y+1][0] == "w":
potential_moves.append([x+1], [y+1])
except IndexError:
pass

try:
if board[x+1][y-1][0] == "w":
potential_moves.append([x+1], [y-1])
except IndexError:
pass

# ------------------------------------------------------------------------------

# resolve knight moves. 8 possible moves maximally

Solution

Thinking about it, I bet a lot of it could be cut right down if the function actually didn't care about moves going off-board, like you say, and that was tidied up later.

Hmm. Probably people who have written chess games before have much better ideas, but here are my thoughts. (Yeah, I've been replacing the nested lists of lists with tuples).

Pawns

The try / except / pass blocks add a lot of length and not a lot of features. If the board width and height go 0-7 then replace them with inline safety checks.

# resolve pawn moves. 4 possible moves maximally
if board[x][y] == "bPawn":

    potential_moves.append((x+1, y))

    if x == 1:  # if the pawn is in the second rank (has not moved)
        potential_moves.append((x+2, y))

    if x < 7 and y < 7 and board[x+1][y+1][0] == "w":
        potential_moves.append((x+1, y+1))

    if x < 7 and 0 < y and board[x+1][y-1][0] == "w":
        potential_moves.append((x+1, y-1))


(I'm not clear why the pawn can move up-right, and down-right, instead of up-right and up-left - probably the game is going left/right on the board, I was assuming it was up/down, if that matters. I'm also not clear why it checks the square to see if the square is white (or has a white piece?) but I'm leaving those as is).

Knights

Code just looks heavy and redundant. Make use of list.extend() / + overload for lists:

# resolve knight moves. 8 possible moves maximally
elif board[x][y] == "bKnight":
    potential_moves += [(x+2, y+1), (x+2, y-1)
                       ,(x+1, y+2), (x+1, y-2)
                       ,(x-2, y-1), (x-2, y+1)
                       ,(x-1, y+2), (x-1, y-2)]


(NB. your Knight code has a bug duplicating the pair (x+1, y+2) instead of the second one being (x+1, y-2)).

Bishops

Right, foothills navigated, what about Bishops? They travel in a big X shape. You do 50+ lines of code to handle each direction from the center, all with two counters and all with safety checks for going off-board. I think you can ditch most of this.

So the Bishop moves diagonally - x+1 and y+1. The longest distance is from one of the corners to the other, so from the bottom left 0,0 to x+7, y+7. All the moves are mirrored vertically and horizontally. So how about:

elif board[x][y] == "bBishop":
    ur = [(x+i, y+i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    dr = [(x+i, y-i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    ul = [(x-i, y+i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    dl = [(x-i, y-i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    potential_moves += ur + dr + ul + dl  #ur = up/right, etc.


I can't tell what's happening in your Bishops code with the check for squares starting with "w", whether it's supposed to indicate a white square or a white piece on the square. But you don't seem to do anything to indicate that the bishop can take a piece and move to x+2,y+2 or similar, so it doesn't seem to be checking for a piece. Although that move is already in the valid move list anyway. So the check doesn't seem to add anything - if the board is normal, how could the diagonal ever go to a different coloured square?

Rooks

That check for a "w" square makes even less sense to me here, since a Rook is not always on the same colour - so why does your code only allow the bRook to move to w squares ?

Same approach as for Bishops, make a big + and constrain it by the board dimensions:

elif board[x][y] == "bRook":
    u = [(x, y+i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    d = [(x, y-i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    l = [(x-i, y) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    r = [(x+i, y) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    potential_moves += u + d + l + r


You can quite possibly sink these down into one list comprehension, and then undo the nested tuples, e.g.

elif board[x][y] == "bRook":
    moves = [((x, y+i),(x, y-i),(x-i, y),(x+i, y)) for i in range(1, 8) if 0<=x<8 and 0<=y<8]
    for x in moves:
        potential_moves += x


Queen

You can do the same with the Queen, although you can do what @vnp suggested - move the Bishop and Rook out into dedicated functions, and make the Queen's moves the combined moves of both of them.

King

You can do the same change with King as with Knight.

I haven't considered the missing functionality @vnp commented on.

Code Snippets

# resolve pawn moves. 4 possible moves maximally
if board[x][y] == "bPawn":

    potential_moves.append((x+1, y))

    if x == 1:  # if the pawn is in the second rank (has not moved)
        potential_moves.append((x+2, y))

    if x < 7 and y < 7 and board[x+1][y+1][0] == "w":
        potential_moves.append((x+1, y+1))

    if x < 7 and 0 < y and board[x+1][y-1][0] == "w":
        potential_moves.append((x+1, y-1))
# resolve knight moves. 8 possible moves maximally
elif board[x][y] == "bKnight":
    potential_moves += [(x+2, y+1), (x+2, y-1)
                       ,(x+1, y+2), (x+1, y-2)
                       ,(x-2, y-1), (x-2, y+1)
                       ,(x-1, y+2), (x-1, y-2)]
elif board[x][y] == "bBishop":
    ur = [(x+i, y+i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    dr = [(x+i, y-i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    ul = [(x-i, y+i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    dl = [(x-i, y-i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    potential_moves += ur + dr + ul + dl  #ur = up/right, etc.
elif board[x][y] == "bRook":
    u = [(x, y+i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    d = [(x, y-i) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    l = [(x-i, y) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    r = [(x+i, y) for i in range(1,8) if 0<=x<8 and 0<=y<8]
    potential_moves += u + d + l + r
elif board[x][y] == "bRook":
    moves = [((x, y+i),(x, y-i),(x-i, y),(x+i, y)) for i in range(1, 8) if 0<=x<8 and 0<=y<8]
    for x in moves:
        potential_moves += x

Context

StackExchange Code Review Q#94465, answer score: 8

Revisions (0)

No revisions yet.