Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added __init__.py
Empty file.
143 changes: 125 additions & 18 deletions chesslib/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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()
Expand All @@ -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]
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Loading