diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chesslib/board.py b/chesslib/board.py index 174168f..9a4e1a7 100644 --- a/chesslib/board.py +++ b/chesslib/board.py @@ -25,10 +25,14 @@ class Board(dict): TODO: * PGN export - * En passant - * Castling - * Promoting pawns + * En passant (Done TJS) + * Castling (Done TJS) + * Promoting pawns (Done TJS) + * 3-time repition (Done TJS) * Fifty-move rule + * Take-backs + * row/column lables + * captured piece imbalance (show how many pawns pieces player is up) ''' axis_y = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H') @@ -40,12 +44,12 @@ class Board(dict): en_passant = '-' halfmove_clock = 0 fullmove_number = 1 - history = [] - def __init__(self, fen = None): if fen is None: self.load(FEN_STARTING) else: self.load(fen) - + self.last_move = None ## to support en passent + self.positions = [None] + def __getitem__(self, coord): if isinstance(coord, str): coord = coord.upper() @@ -65,7 +69,7 @@ def is_in_check_after_move(self, p1, p2): tmp._do_move(p1,p2) return tmp.is_in_check(self[p1].color) - def move(self, p1, p2): + def move(self, p1, p2, promote='q'): p1, p2 = p1.upper(), p2.upper() piece = self[p1] dest = self[p2] @@ -78,33 +82,93 @@ def move(self, p1, p2): # 0. Check if p2 is in the possible moves if p2 not in possible_moves: raise InvalidMove - + # If enemy has any moves look for check if self.all_possible_moves(enemy): if self.is_in_check_after_move(p1,p2): raise Check - if not possible_moves and self.is_in_check(piece.color): raise CheckMate elif not possible_moves: raise Draw else: - self._do_move(p1, p2) + self._do_move(p1, p2, promote) self._finish_move(piece, dest, p1,p2) + if self.positions[-1] in self.positions[:-1]: + count = 1 + for position in self.positions[:-1]: + count += (position == self.positions[-1]) + print 'repetition count:', count + if count >= 3: + raise Draw def get_enemy(self, color): if color == "white": return "black" else: return "white" - def _do_move(self, p1, p2): + def is_en_passent(self, p1, p2, piece): + ''' + return False if move is not an en passent, otherwise return square to capture on + ''' + out = False + ## pawn to blank space + move = p1 + p2 + if not self.is_pawn(piece) or self[move[2:4]] is not None: + out = False + elif move[1] == '5' and move[3] == '6' and move[0] != move[2]: ## white diag + out = move[2] + '5' + elif move[1] == '4' and move[3] == '3' and move[0] != move[2]: ## black diag + out = move[2] + '4' + return out + + def is_castle(self, p1, p2, piece): + '''return move for castle if it is else False''' + move = p1.upper() + p2.upper() + if not self.is_king(piece): + out = False + elif move == 'E1G1' and piece.color == 'white': + out = 'H1F1' + elif move == 'E1C1' and piece.color == 'white': + out = 'A1D1' + elif move == 'E8G8' and piece.color == 'black': + out = 'H8F8' + elif move == 'E8C8' and piece.color == 'black': + out = 'A8D8' + else: + out = False + return out + + def _do_move(self, p1, p2, promote='q'): ''' Move a piece without validation ''' piece = self[p1] + if self.is_king(piece): + piece.can_castle = False + if self.is_rook(piece): + piece.can_castle = False + dest = self[p2] + ## if piece is a king and move is a castle, move the rook too + castle = self.is_castle(p1, p2, piece) + if castle: + move = castle + self._do_move(move[:2], move[2:]) + en_passent = self.is_en_passent(p1, p2, piece) + if en_passent: + del self[en_passent] del self[p1] + self.last_move = (p1, p2) + ## check pawn promotion + if self.is_pawn(piece) and p2[1] in '18': + piece = pieces.Pieces[promote.upper()](piece.color) + piece.board = self self[p2] = piece + ## for three-fold repetiion + fen = self.export().split() + self.positions.append(fen[0] + fen[1]) + def _finish_move(self, piece, dest, p1, p2): ''' Set next player turn, count moves, log moves, etc. @@ -129,9 +193,6 @@ def _finish_move(self, piece, dest, p1, p2): # Capturing resets halfmove_clock self.halfmove_clock = 0 - self.history.append(movetext) - - def all_possible_moves(self, color): ''' Return a list of `color`'s possible moves. @@ -160,6 +221,12 @@ def occupied(self, color): def is_king(self, piece): return isinstance(piece, pieces.King) + def is_rook(self, piece): + return isinstance(piece, pieces.Rook) + + def is_pawn(self, piece): + return isinstance(piece, pieces.Pawn) + def get_king_position(self, color): for pos in self.keys(): @@ -191,7 +258,10 @@ def is_in_bounds(self, coord): coord[0] < 0 or coord[0] > 7: return False else: return True - + def clear(self): + dict.clear(self) + self.poistions = [None] + def load(self, fen): ''' Import state from FEN notation @@ -203,7 +273,7 @@ def load(self, fen): def expand(match): return ' ' * int(match.group(0)) fen[0] = re.compile(r'\d').sub(expand, fen[0]) - + self.positions = [None] for x, row in enumerate(fen[0].split('/')): for y, letter in enumerate(row): if letter == ' ': continue @@ -219,6 +289,17 @@ def expand(match): return ' ' * int(match.group(0)) self.halfmove_clock = int(fen[4]) self.fullmove_number = int(fen[5]) + def can_en_passent(self): + out = '-' + if self.last_move and abs(int(self.last_move[1][1]) - int(self.last_move[0][1])) == 2: + if self.is_pawn(self[self.last_move[1]]): ### yes we can + out = self.last_move[1][0].lower() + if self.last_move[1][1] == '4': + out += '3' + else: + out += '6' + return out + def export(self): ''' Export state to FEN notation @@ -242,11 +323,37 @@ def replace_spaces(row): else: result += ' ' result += '/' + castling = '' + if self.is_king(self['E1']) and self.is_rook(self['H1']): + king = self['E1'] + rook = self['H1'] + if king.can_castle and rook.can_castle: + castling += 'K' + if self.is_king(self['E1']) and self.is_rook(self['A1']): + king = self['E1'] + rook = self['A1'] + if king.can_castle and rook.can_castle: + castling += 'Q' + if self.is_king(self['E8']) and self.is_rook(self['H8']): + king = self['E8'] + rook = self['H8'] + if king.can_castle and rook.can_castle: + castling += 'k' + if self.is_king(self['E8']) and self.is_rook(self['A8']): + king = self['E8'] + rook = self['A8'] + if king.can_castle and rook.can_castle: + castling += 'q' + if castling == '': + castling = '-' + + en_passent = self.can_en_passent() + result = result[:-1] # remove trailing "/" result = replace_spaces(result) result += " " + (" ".join([self.player_turn[0], - self.castling, - self.en_passant, + castling, + en_passent, str(self.halfmove_clock), str(self.fullmove_number)])) return result diff --git a/chesslib/gui_tkinter.py b/chesslib/gui_tkinter.py index 8b0b84a..008dea5 100644 --- a/chesslib/gui_tkinter.py +++ b/chesslib/gui_tkinter.py @@ -1,31 +1,86 @@ +import os +import glob import board import pieces import Tkinter as tk from PIL import Image, ImageTk +color1 ='white' +color2 = 'grey' + +def get_color_from_coords(coords): + return [color1, color2][(coords[0] - coords[1]) % 2] + class BoardGuiTk(tk.Frame): pieces = {} selected = None selected_piece = None - hilighted = None + hilighted = None icons = {} - color1 = "white" - color2 = "grey" - rows = 8 columns = 8 + def ask_piece(self, color): + if not self.prompting: + top = tk.Toplevel() + codes = 'QRBN' + def on_click(event): + x = event.x // self.square_size + if 0 <= x and x < len(codes): + self.promote_code = codes[x] + top.destroy() + + canvas = tk.Canvas(top, + width=len(codes) * self.square_size, + height=self.square_size, + background='grey') + canvas.bind('', on_click) + canvas.pack(side="top", fill="both", anchor="c", expand=True) + x = -self.square_size / 2 + y = self.square_size / 2 + for i, code in enumerate(codes): + x += self.square_size + canvas.create_rectangle(i * self.square_size, 0, + (i + 1) * self.square_size, self.square_size, + outline="black", + fill=[color1, color2][i % 2], + tags="square") + filename = "img/%s%s.png" % (color, code.lower()) + piecename = "%s%s%s" % (code.lower, x, y) + canvas.create_image(x, y, image=self.icons[filename], tags=("piece",), anchor="c") + self.prompting = True + parent_geom = self.parent.geometry() + wh, x, y = parent_geom.split('+') + x = int(x) + y = int(y) + if x < 0: + x = 0 + if y < 0: + y = 0 + w, h = wh.split('x') + w = int(w) + h = int(h) + + geom = "%dx%d%+d%+d" % (len(codes) * self.square_size, + self.square_size, x, y) + top.geometry(geom) + self.wait_window(top) + self.prompting = False @property def canvas_size(self): return (self.columns * self.square_size, self.rows * self.square_size) def __init__(self, parent, chessboard, square_size=64): - + self.color1 = color1 + self.color2 = color2 self.chessboard = chessboard self.square_size = square_size self.parent = parent + self.from_square = None + self.to_square = None + self.prompting = False canvas_width = self.columns * square_size canvas_height = self.rows * square_size @@ -53,6 +108,21 @@ def __init__(self, parent, chessboard, square_size=64): self.statusbar.pack(expand=False, fill="x", side='bottom') + def redraw_square(self, coord, color=None): + row, col = coord + if color is None: + color = get_color_from_coords(coord) + x1 = (col * self.square_size) + y1 = ((7-row) * self.square_size) + x2 = x1 + self.square_size + y2 = y1 + self.square_size + self.canvas.create_rectangle(x1, y1, x2, y2, outline="black", fill=color, tags="square") + + ## see if we need to redraw a piece + piece = self.chessboard[(row, col)] + if piece is not None: + self.draw_piece(piece, row, col) + def click(self, event): # Figure out which square we've clicked @@ -60,9 +130,12 @@ def click(self, event): current_column = event.x / col_size current_row = 7 - (event.y / row_size) - + position = self.chessboard.letter_notation((current_row, current_column)) piece = self.chessboard[position] + if self.from_square is not None: ## undraw from + self.redraw_square(self.from_square) + self.redraw_square(self.to_square) if self.selected_piece: self.move(self.selected_piece[1], position) @@ -71,31 +144,79 @@ def click(self, event): self.pieces = {} self.refresh() self.draw_pieces() - + else: + self.hilighted = None self.hilight(position) self.refresh() - def move(self, p1, p2): + if self.from_square is not None: + self.redraw_square(self.from_square, 'tan1') + self.redraw_square(self.to_square, 'tan1') + if self.hilighted is not None: + for square in self.hilighted: + self.redraw_square(square, 'spring green') + enemy = self.chessboard.player_turn + if self.chessboard.is_in_check(enemy): + algebraic_pos = self.chessboard.get_king_position(enemy) + enemy_king_loc = self.chessboard.number_notation(algebraic_pos) + self.redraw_square(enemy_king_loc, 'red') + + + def move(self, p1, p2, promote=None): + piece = self.chessboard[p1] + enemy = self.chessboard.get_enemy(piece.color) dest_piece = self.chessboard[p2] if dest_piece is None or dest_piece.color != piece.color: try: - self.chessboard.move(p1,p2) + if self.from_square: + self.redraw_square(self.from_square) + if self.to_square: + self.redraw_square(self.to_square) + if isinstance(piece, pieces.Pawn) and p2[1] in '18': + ### promotion! + if promote is None: + self.ask_piece(piece.color) + promote = self.promote_code + else: + promote = None + self.chessboard.move(p1, p2, promote=promote) + self.from_square = self.chessboard.number_notation(p1) + self.to_square = self.chessboard.number_notation(p2) + self.redraw_square(self.from_square, 'tan1') + self.redraw_square(self.to_square, 'tan1') + + if self.chessboard.is_in_check(enemy): + algebraic_pos = self.chessboard.get_king_position(enemy) + enemy_king_loc = self.chessboard.number_notation(algebraic_pos) + self.redraw_square(enemy_king_loc, 'red') + + except board.InvalidMove as error: + self.hilighted = [] except board.ChessError as error: + print 'ChessError', error.__class__.__name__ self.label_status["text"] = error.__class__.__name__ + self.refresh() + raise else: self.label_status["text"] = " " + piece.color.capitalize() +": "+ p1 + p2 - def hilight(self, pos): piece = self.chessboard[pos] if piece is not None and (piece.color == self.chessboard.player_turn): self.selected_piece = (self.chessboard[pos], pos) - self.hilighted = map(self.chessboard.number_notation, (self.chessboard[pos].possible_moves(pos))) + possible_moves = self.chessboard[pos].possible_moves(pos) + possible_moves = [m for m in possible_moves if + not self.chessboard.is_in_check_after_move(pos, m)] + self.hilighted = map(self.chessboard.number_notation, possible_moves) def addpiece(self, name, image, row=0, column=0): '''Add a piece to the playing board''' - self.canvas.create_image(0,0, image=image, tags=(name, "piece"), anchor="c") + # self.canvas.create_image(0,0, image=image, tags=(name, "piece"), anchor="c") + x = ((column + .5) * self.square_size) + y = ((7-(row - .5)) * self.square_size) + + self.canvas.create_image(x, y, image=image, tags=(name, "piece"), anchor="c") self.placepiece(name, row, column) def placepiece(self, name, row, column): @@ -122,30 +243,35 @@ def refresh(self, event={}): x2 = x1 + self.square_size y2 = y1 + self.square_size if (self.selected is not None) and (row, col) == self.selected: - self.canvas.create_rectangle(x1, y1, x2, y2, outline="black", fill="orange", tags="square") + self.redraw_square((row, col), 'orange') elif(self.hilighted is not None and (row, col) in self.hilighted): - self.canvas.create_rectangle(x1, y1, x2, y2, outline="black", fill="spring green", tags="square") + self.redraw_square((row, col), 'spring green') else: - self.canvas.create_rectangle(x1, y1, x2, y2, outline="black", fill=color, tags="square") + self.redraw_square((row, col), color) color = self.color1 if color == self.color2 else self.color2 for name in self.pieces: self.placepiece(name, self.pieces[name][0], self.pieces[name][1]) self.canvas.tag_raise("piece") self.canvas.tag_lower("square") + def draw_piece(self, piece, row, col): + x = (col * self.square_size) + y = ((7-row) * self.square_size) + filename = "img/%s%s.png" % (piece.color, piece.abbriviation.lower()) + piecename = "%s%s%s" % (piece.abbriviation, x, y) + + if(filename not in self.icons): + self.icons[filename] = ImageTk.PhotoImage(file=filename, width=32, height=32) + self.addpiece(piecename, self.icons[filename], row, col) + #self.placepiece(piecename, row, col) + def draw_pieces(self): self.canvas.delete("piece") + for coord, piece in self.chessboard.iteritems(): - x,y = self.chessboard.number_notation(coord) if piece is not None: - filename = "img/%s%s.png" % (piece.color, piece.abbriviation.lower()) - piecename = "%s%s%s" % (piece.abbriviation, x, y) - - if(filename not in self.icons): - self.icons[filename] = ImageTk.PhotoImage(file=filename, width=32, height=32) - - self.addpiece(piecename, self.icons[filename], x, y) - self.placepiece(piecename, x, y) + x,y = self.chessboard.number_notation(coord) + self.draw_piece(piece, x, y) def reset(self): self.chessboard.load(board.FEN_STARTING) diff --git a/chesslib/pieces.py b/chesslib/pieces.py index e8f1bc3..05a0085 100644 --- a/chesslib/pieces.py +++ b/chesslib/pieces.py @@ -28,6 +28,7 @@ class Piece(object): __slots__ = ('abbriviation', 'color') def __init__(self, color): + self.can_castle = False if color == 'white': self.abbriviation = self.abbriviation.upper() elif color == 'black': @@ -115,7 +116,15 @@ def possible_moves(self, position): if board.letter_notation(attack) in board.occupied(enemy): legal_moves.append(attack) - # TODO: En passant + ## TJS: check for en passent + en_passent = board.can_en_passent() + if en_passent != '-': + if self.color == 'white' and position[1] == '5': + if abs(ord(position[0].lower()) - ord(en_passent[0].lower())) == 1: + legal_moves.append((int(en_passent[1]) - 1, ord(en_passent[0].lower()) - ord('a'))) + if self.color == 'black' and position[1] == '4': + if abs(ord(position[0].lower()) - ord(en_passent[0].lower())) == 1: + legal_moves.append((int(en_passent[1]) - 1, ord(en_passent[0].lower()) - ord('a'))) legal_moves = filter(board.is_in_bounds, legal_moves) return map(board.letter_notation, legal_moves) @@ -141,6 +150,9 @@ def possible_moves(self,position): class Rook(Piece): abbriviation = 'r' + def __init__(self, color): + Piece.__init__(self, color) + self.can_castle = True def possible_moves(self,position): position = position.upper() return super(Rook, self).possible_moves(position, True, False, 8) @@ -160,6 +172,39 @@ def possible_moves(self,position): class King(Piece): abbriviation = 'k' move_length = 1 + def __init__(self, color): + Piece.__init__(self, color) + self.can_castle = True + def possible_moves(self,position): position = position.upper() - return super(King, self).possible_moves(position, True, True, 1) + out = super(King, self).possible_moves(position, True, True, 1) + ## check castling + if self.can_castle: + if self.color == 'white': + if self.board['H1'] is not None and self.board['H1'].can_castle: + if self.board['F1'] is None and self.board['G1'] is None: + out.append('G1') + if self.board['A1'] is not None and self.board['A1'].can_castle: + if (self.board['B1'] is None and + self.board['C1'] is None and + self.board['D1'] is None): + out.append('C1') + else: + if self.board['H8'] is not None and self.board['H8'].can_castle: + if self.board['F8'] is None and self.board['G8'] is None: + out.append('G8') + if self.board['A8'] is not None and self.board['A8'].can_castle: + if (self.board['B8'] is None and + self.board['C8'] is None and + self.board['D8'] is None): + out.append('C8') + return out +Pieces = { + 'P':Pawn, + 'R':Rook, + 'N':Knight, + 'B':Bishop, + 'Q':Queen, + 'K':King +} diff --git a/gui_console.py b/gui_console.py new file mode 100644 index 0000000..e0683f5 --- /dev/null +++ b/gui_console.py @@ -0,0 +1,59 @@ +# -*- encoding: utf-8 -*- +import board +import os + +UNICODE_PIECES = { + 'r': u'♜', 'n': u'♞', 'b': u'♝', 'q': u'♛', + 'k': u'♚', 'p': u'♟', 'R': u'♖', 'N': u'♘', + 'B': u'♗', 'Q': u'♕', 'K': u'♔', 'P': u'♙', + None: ' ' +} + +class BoardGuiConsole(object): + ''' + Print a text-mode chessboard using the unicode chess pieces + ''' + error = '' + + def __init__(self, chessboard): + self.board = chessboard + + def move(self): + os.system("clear") + self.unicode_representation() + print "\n", self.error + print "State a move in chess notation (e.g. A2A3). Type \"exit\" to leave:\n", ">>>", + self.error = '' + coord = raw_input() + if coord == "exit": + print "Bye." + exit(0) + try: + if len(coord) != 4: raise board.InvalidCoord + self.board.move(coord[0:2], coord[2:4]) + os.system("clear") + except board.ChessError as error: + self.error = "Error: %s" % error.__class__.__name__ + + self.move() + + def unicode_representation(self): + print "\n", ("%s's turn\n" % self.board.player_turn.capitalize()).center(28) + for number in self.board.axis_x[::-1]: + print " " + str(number) + " ", + for letter in self.board.axis_y: + piece = self.board[letter+str(number)] + if piece is not None: + print UNICODE_PIECES[piece.abbriviation] + ' ', + else: print ' ', + print "\n" + print " " + " ".join(self.board.axis_y) + + +def display(board): + try: + gui = BoardGuiConsole(board) + gui.move() + except (KeyboardInterrupt, EOFError): + os.system("clear") + exit(0) diff --git a/pieces.py b/pieces.py new file mode 100644 index 0000000..05a0085 --- /dev/null +++ b/pieces.py @@ -0,0 +1,210 @@ +import pieces +import sys + +ABBRIVIATIONS = { + 'R':'Rook', + 'N':'Knight', + 'B':'Bishop', + 'Q':'Queen', + 'K':'King', + 'P':'Pawn' +} + +class InvalidPiece(Exception): pass +class InvalidColor(Exception): pass + +def piece(piece, color='white'): + ''' Takes a piece name or abbriviation and returns the corresponding piece instance ''' + if piece in (None, ' '): return + if len(piece) == 1: + # We have an abbriviation + if piece.isupper(): color = 'white' + else: color = 'black' + piece = ABBRIVIATIONS[piece.upper()] + module = sys.modules[__name__] + return module.__dict__[piece](color) + +class Piece(object): + __slots__ = ('abbriviation', 'color') + + def __init__(self, color): + self.can_castle = False + if color == 'white': + self.abbriviation = self.abbriviation.upper() + elif color == 'black': + self.abbriviation = self.abbriviation.lower() + + try: + self.color = color + except KeyError: + raise InvalidColor + + @property + def name(self): return self.__class__.__name__ + def place(self, board): + ''' Keep a reference to the board ''' + self.board = board + + def possible_moves(self, position, orthogonal, diagonal, distance): + board = self.board + legal_moves = [] + orth = ((-1,0),(0,-1),(0,1),(1,0)) + diag = ((-1,-1),(-1,1),(1,-1),(1,1)) + piece = self + + from_ = board.number_notation(position) + if orthogonal and diagonal: + directions = diag+orth + elif diagonal: + directions = diag + elif orthogonal: + directions = orth + + for x,y in directions: + collision = False + for step in range(1, distance+1): + if collision: break + dest = from_[0]+step*x, from_[1]+step*y + if self.board.letter_notation(dest) not in board.occupied('white') + board.occupied('black'): + legal_moves.append(dest) + elif self.board.letter_notation(dest) in board.occupied(piece.color): + collision = True + else: + legal_moves.append(dest) + collision = True + + legal_moves = filter(board.is_in_bounds, legal_moves) + return map(board.letter_notation, legal_moves) + + def __str__(self): + return self.abbriviation + + def __repr__(self): + return "<" + self.color.capitalize() + " " + self.__class__.__name__ + ">" + +class Pawn(Piece): + abbriviation = 'p' + def possible_moves(self, position): + board = self.board + position = position.upper() + piece = self + if self.color == 'white': + homerow, direction, enemy = 1, 1, 'black' + else: + homerow, direction, enemy = 6, -1, 'white' + + legal_moves = [] + + # Moving + + blocked = board.occupied('white') + board.occupied('black') + from_ = board.number_notation(position) + forward = from_[0] + direction, from_[1] + + # Can we move forward? + if board.letter_notation(forward) not in blocked: + legal_moves.append(forward) + if from_[0] == homerow: + # If pawn in starting position we can do a double move + double_forward = (forward[0] + direction, forward[1]) + if board.letter_notation(double_forward) not in blocked: + legal_moves.append(double_forward) + + # Attacking + for a in range(-1, 2, 2): + attack = from_[0] + direction, from_[1] + a + if board.letter_notation(attack) in board.occupied(enemy): + legal_moves.append(attack) + + ## TJS: check for en passent + en_passent = board.can_en_passent() + if en_passent != '-': + if self.color == 'white' and position[1] == '5': + if abs(ord(position[0].lower()) - ord(en_passent[0].lower())) == 1: + legal_moves.append((int(en_passent[1]) - 1, ord(en_passent[0].lower()) - ord('a'))) + if self.color == 'black' and position[1] == '4': + if abs(ord(position[0].lower()) - ord(en_passent[0].lower())) == 1: + legal_moves.append((int(en_passent[1]) - 1, ord(en_passent[0].lower()) - ord('a'))) + legal_moves = filter(board.is_in_bounds, legal_moves) + return map(board.letter_notation, legal_moves) + + +class Knight(Piece): + abbriviation = 'n' + def possible_moves(self,position): + board = self.board + position = position.upper() + legal_moves = [] + from_ = board.number_notation(position) + piece = board.get(position) + deltas = ((-2,-1),(-2,1),(-1,-2),(-1,2),(1,-2),(1,2),(2,-1),(2,1)) + + for x,y in deltas: + dest = from_[0]+x, from_[1]+y + if(board.letter_notation(dest) not in board.occupied(piece.color)): + legal_moves.append(dest) + + legal_moves = filter(board.is_in_bounds, legal_moves) + return map(board.letter_notation, legal_moves) + + +class Rook(Piece): + abbriviation = 'r' + def __init__(self, color): + Piece.__init__(self, color) + self.can_castle = True + def possible_moves(self,position): + position = position.upper() + return super(Rook, self).possible_moves(position, True, False, 8) + +class Bishop(Piece): + abbriviation = 'b' + def possible_moves(self,position): + position = position.upper() + return super(Bishop,self).possible_moves(position, False, True, 8) + +class Queen(Piece): + abbriviation = 'q' + def possible_moves(self,position): + position = position.upper() + return super(Queen, self).possible_moves(position, True, True, 8) + +class King(Piece): + abbriviation = 'k' + move_length = 1 + def __init__(self, color): + Piece.__init__(self, color) + self.can_castle = True + + def possible_moves(self,position): + position = position.upper() + out = super(King, self).possible_moves(position, True, True, 1) + ## check castling + if self.can_castle: + if self.color == 'white': + if self.board['H1'] is not None and self.board['H1'].can_castle: + if self.board['F1'] is None and self.board['G1'] is None: + out.append('G1') + if self.board['A1'] is not None and self.board['A1'].can_castle: + if (self.board['B1'] is None and + self.board['C1'] is None and + self.board['D1'] is None): + out.append('C1') + else: + if self.board['H8'] is not None and self.board['H8'].can_castle: + if self.board['F8'] is None and self.board['G8'] is None: + out.append('G8') + if self.board['A8'] is not None and self.board['A8'].can_castle: + if (self.board['B8'] is None and + self.board['C8'] is None and + self.board['D8'] is None): + out.append('C8') + return out +Pieces = { + 'P':Pawn, + 'R':Rook, + 'N':Knight, + 'B':Bishop, + 'Q':Queen, + 'K':King +} diff --git a/stockfish_chess.py b/stockfish_chess.py new file mode 100644 index 0000000..87daad6 --- /dev/null +++ b/stockfish_chess.py @@ -0,0 +1,121 @@ +import time +from Tkinter import * +from chesslib import board +from chesslib import gui_tkinter +import subprocess +import argparse + +parser = argparse.ArgumentParser(description='Play a game of chess') +parser.add_argument("--depth", type=int, + help='stockfish search depth', + default=10) +parser.add_argument("--n_player", type=int, + help='number of human players', + default=0) +args = parser.parse_args() +current_proc = None +depth = args.depth +n_player = args.n_player + +def getEngine(): + global current_proc, depth + if current_proc is not None: + if current_proc.poll() is not None: + raise ValueError('Engine died') + if current_proc is None or current_proc.poll() is not None: + current_proc = subprocess.Popen('stockfish', stdout=subprocess.PIPE, stdin=subprocess.PIPE) + current_proc.stdin.write('uci\n') + depth += 1 + return current_proc + + +moves = [] +def isEnPassent(move, piece): + out = False + ## pawn to blank space + if piece.abbriviation.lower() != 'p' or game[move[2:4]] is not None: + out = False + elif move[1] == '5' and move[3] == '6' and move[0] != move[2]: ## white diag + out = move[2] + '5' + elif move[1] == '4' and move[3] == '3' and move[0] != move[2]: ## black diag + out = move[2] + '3' + return out + +def isCastle(move, piece): + '''return move for castle if it is else False''' + move = move.upper() + if piece.abbriviation.lower() != 'k': + out = False + elif move == 'E1G1' and piece.color == 'white': + out = 'H1F1' + elif move == 'E1C1' and piece.color == 'white': + out = 'A1D1' + elif move == 'E8G8' and piece.color == 'black': + out = 'H8F8' + elif move == 'E8C8' and piece.color == 'black': + out = 'A8D8' + else: + out = False + return out + +def go(): + if n_player == 2: + return + elif n_player == 1 and game.export().split()[1] == 'w': + r.after(1000, go) + return + engine = getEngine() + fen = game.export() + command = 'position fen %s\n' % fen + # print '<<<', command + engine.stdin.write(command) + engine.stdin.write('go depth %d\n' % depth) + line = '' + while 'bestmove' not in line and engine.poll() is None: + if line: + pass + # print '>>>', line.strip(), engine.poll() + line = engine.stdout.readline() + if line == '': + break + if 'bestmove' in line: + # print '>>>', line + words = line.split() + move = words[1].upper() + if move == '(NONE)': + print 'Game Over!' + return + piece = gui.chessboard[move[:2]] + if len(move) > 4: + promote = move[4] + else: + promote = None + gui.move(move[:2], move[2:4], promote=promote) + + enemy = gui.chessboard.get_enemy(piece.color) + moves.append(move) + # print move, + if piece.color == 'black': + gui.chessboard.fullmove_number += 1 + # print '' + gui.chessboard.halfmove_clock +=1 + gui.chessboard.player_turn = enemy + gui.refresh() + gui.draw_pieces() + gui.redraw_square(gui.from_square, 'tan1') + gui.redraw_square(gui.to_square, 'tan1') + if gui.chessboard.is_in_check(enemy): + p = gui.chessboard.get_king_position(enemy) + p = gui.chessboard.number_notation(p) + gui.redraw_square(p, 'red') + r.after(100, go) + +game = board.Board() +r = Tk() +r.after(1000, go) +gui = gui_tkinter.BoardGuiTk(r, game) +gui.pack() +gui.draw_pieces() + +r.mainloop() +