First attempt at a Python prog (Chess)
Oscar Benjamin
oscar.j.benjamin at gmail.com
Fri Feb 15 06:22:14 EST 2013
On 13 February 2013 23:25, Chris Hinsley <chris.hinsley at gmail.com> wrote:
> New to Python, which I really like BTW.
>
> First serious prog. Hope you like it. I know it needs a 'can't move if your
> King would be put into check' test. But the weighted value of the King piece
> does a surprising emergent job.
>
> #!/usr/bin/python -tt
> # -*- coding: utf-8 -*-
> # Copyright (C) 2013 Chris Hinsley, GPL V3 License
>
> import sys
> import random
> import os
>
> PLY = 3
>
> EMPTY = 0
> BLACK = 1
> WHITE = 2
> NO_CAPTURE = 3
> MAY_CAPTURE = 4
> MUST_CAPTURE = 5
>
> def piece_type(piece):
> return EMPTY if piece == 32 else BLACK if chr(piece) in 'KQRBNP' else
> WHITE
You call chr(piece) many times in this program. It would be better to
just have piece be a string rather than always converting it to one
every time you want to do something. Also comparing it with a numeric
code is fairly cryptic. I guess that ascii 32 is a space character but
I'd have to look that up to be sure.
>
> def display_board(board):
> print ' a b c d e f g h'
> print '+---+---+---+---+---+---+---+---+'
> for row in range(8):
> for col in range(8):
Why not make board a list of lists. Then you can do:
for row in board:
for piece in row:
rather than using range().
Or perhaps you could have a dict that maps position tuples to pieces,
e.g.: {(1, 2): 'k', ...}
> sys.stdout.write('| ')
> sys.stdout.write(chr(board[row * 8 + col]))
> sys.stdout.write(' ')
> sys.stdout.write('|')
> print 8 - row
> print '+---+---+---+---+---+---+---+---+'
You seem to be using sys.stdout.write as a way of printing without a
trailing newline. In Python 2 you can get this effect by using:
print 'foo',
(note the trailing comma). In Python 3 you would do
print('foo', end=' ')
You can use the Python 3 syntax in your Python 2 script if you do
"from __future__ import print_function" so that your script works on
Python 2 and 3.
Also I would probably separate the function that generates the text
representing the board from the code that actually sends that
information to stdout.
>
> def piece_moves(board, index, dx, dy, capture_flag, distance):
> piece = board[index]
> type = piece_type(piece)
> cx = index % 8
> cy = index / 8
You can use divmod for this:
cx, cy = divmod(index, 8)
Also in Python 3 index / 8 will return a float. Use // for floor
division in both versions ("from __future__ import division").
> for step in range(distance):
> nx = cx + (dx * (step + 1))
> ny = cy + (dy * (step + 1))
Why not make a function that yields these values and loop over that?
def continued_moves(x, y, dx, dy):
while 0 <= x < 8 and 0 <= y < 8:
x += dx
y += dy
yield x, y
> if nx in range(8) and ny in range(8):
Use chained comparisons 0 <= x < 8 rather than testing for membership
in a range object. "x in range(N, M)" creates (in Python 2) a list
integers and then (in 2 or 3) iterates over that list to find an
object equal to x. This is inefficient and not as clear.
> newindex = ny * 8 + nx
> newpiece = board[newindex]
With a list of lists you could access the board with board[ny][nx]
which is clearer. Or with the dict: board[(nx, ny)].
The code below is overly indented. consider factoring it into functions.
> newtype = piece_type(newpiece)
> if capture_flag == MUST_CAPTURE:
> if newtype != EMPTY and newtype != type:
> board[index] = ' '
> if (ny == 0 or ny == 7) and chr(piece) in 'Pp':
> for promote in 'QRBN' if type == BLACK else 'qrbn':
> board[newindex] = promote
> yield board
> else:
> board[newindex] = piece
> yield board
> board[index], board[newindex] = piece, newpiece
> elif capture_flag == MAY_CAPTURE:
> if newtype == EMPTY or newtype != type:
> board[index], board[newindex] = ' ', piece
> yield board
> board[index], board[newindex] = piece, newpiece
Rather than modifying and unmodifying the board in place (which is
fragile), could you not just have the compute_score function compute
the score as if the move had taken place? Then you could just yield
the move and the score.
> break
> elif newtype == EMPTY:
> board[index] = ' '
> if (ny == 0 or ny == 7) and chr(piece) in 'Pp':
> for promote in 'QRBN' if type == BLACK else 'qrbn':
> board[newindex] = promote
> yield board
> else:
> board[newindex] = piece
> yield board
> board[index], board[newindex] = piece, newpiece
> else:
> break
>
> def pawn_moves(board, index, options):
> for x, y, flag, distance in options:
> for new_board in piece_moves(board, index, x, y, flag, distance):
> yield new_board
>
> def other_moves(board, index, options, distance):
> for x, y in options:
> for new_board in piece_moves(board, index, x, y, MAY_CAPTURE,
> distance):
> yield new_board
>
> def black_pawn_moves(board, index):
> distance = 2 if index in range(8, 16) else 1
> for new_board in pawn_moves(board, index, [(0, 1, NO_CAPTURE, distance),
> (-1, 1, MUST_CAPTURE, 1), (1, 1, MUST_CAPTURE, 1)]):
> yield new_board
>
> def white_pawn_moves(board, index):
> distance = 2 if index in range(48, 56) else 1
> for new_board in pawn_moves(board, index, [(0, -1, NO_CAPTURE, distance),
> (-1, -1, MUST_CAPTURE, 1), (1, -1, MUST_CAPTURE, 1)]):
> yield new_board
Do you really need separate functions for black and white pawns?
>
> def rook_moves(board, index):
> for new_board in other_moves(board, index, [(0, -1), (-1, 0), (0, 1), (1,
> 0)], 7):
> yield new_board
>
> def bishop_moves(board, index):
> for new_board in other_moves(board, index, [(-1, -1), (-1, 1), (1, 1),
> (1, -1)], 7):
> yield new_board
>
> def knight_moves(board, index):
> for new_board in other_moves(board, index, [(-2, 1), (2, -1), (2, 1),
> (-1, -2), (-1, 2), (1, -2), (1, 2)], 1):
> yield new_board
>
> def queen_moves(board, index):
> for new_board in bishop_moves(board, index):
> yield new_board
> for new_board in rook_moves(board, index):
> yield new_board
>
> def king_moves(board, index):
> for new_board in other_moves(board, index, [(0, -1), (-1, 0), (0, 1), (1,
> 0), (-1, -1), (-1, 1), (1, 1), (1, -1)], 1):
> yield new_board
>
> moves = {'P' : black_pawn_moves, 'p' : white_pawn_moves, \
> 'R' : rook_moves, 'r' : rook_moves, \
> 'B' : bishop_moves, 'b' : bishop_moves, \
> 'N' : knight_moves, 'n' : knight_moves, \
> 'Q' : queen_moves, 'q' : queen_moves, \
> 'K' : king_moves, 'k' : king_moves}
>
> def all_moves(board, turn):
> for index, piece in enumerate(board):
> if piece_type(piece) == turn:
> for new_board in moves[chr(piece)](board, index):
> yield new_board
>
> piece_values = {'K' : (1000000, 0), 'k' : (0, 1000000), \
> 'P' : (1, 0), 'p' : (0, 1), \
> 'N' : (3, 0), 'n' : (0, 3), \
> 'B' : (3, 0), 'b' : (0, 3), \
> 'R' : (5, 0), 'r' : (0, 5), \
> 'Q' : (9, 0), 'q' : (0, 9)}
>
> position_values = [0, 0, 0, 0, 0, 0, 0, 0, \
> 0, 1, 1, 1, 1, 1, 1, 0, \
> 0, 1, 2, 2, 2, 2, 1, 0, \
> 0, 1, 2, 3, 3, 2, 1, 0, \
> 0, 1, 2, 3, 3, 2, 1, 0, \
> 0, 1, 2, 2, 2, 2, 1, 0, \
> 0, 1, 1, 1, 1, 1, 1, 0, \
> 0, 0, 0, 0, 0, 0, 0, 0]
>
> def score_board(board):
> black_score, white_score = 0, 0
> for index, piece in enumerate(board):
Iterating over the whole board is wasteful when it is usually empty
the dict approach would be better in this case.
> type = piece_type(piece)
> if type != EMPTY:
> position_value = position_values[index]
> if type == BLACK:
> black_score += position_value
> else:
> white_score += position_value
> black_value, white_value = piece_values[chr(piece)]
> black_score += black_value
> white_score += white_value
> return (black_score, white_score)
>
> def turn_score(board, turn):
> black_score, white_score = score_board(board)
> return (white_score - black_score) if turn == WHITE else (black_score -
> white_score)
>
> def best_move(board, turn, ply):
> best_score = -10000000
best_score = None
> best_boards = []
> for new_board in all_moves(board, turn):
> if ply:
> next_turn = BLACK if turn == WHITE else WHITE
> score = turn_score(best_move(new_board, next_turn, ply - 1),
> turn)
> else:
> score = turn_score(new_board, turn)
> if score > best_score or not best_boards:
if score > best_score or best_score is None:
> best_score = score
> best_boards = [new_board[:]]
> elif score == best_score:
> best_boards.append(new_board[:])
> if best_boards:
> return random.choice(best_boards)
> return board[:]
>
> def main():
> board = bytearray('RNBQKBNRPPPPPPPP
> pppppppprnbqkbnr')
> turn = WHITE
> while True:
> board = best_move(board, turn, PLY)
> turn = BLACK if turn == WHITE else WHITE
> os.system('clear')
> display_board(board)
> #raw_input()
>
> if __name__ == '__main__':
> main()
Oscar
More information about the Python-list
mailing list