Skip to content

Commit bd4ecd5

Browse files
author
sesarr
committed
Initial version
0 parents  commit bd4ecd5

File tree

16 files changed

+628
-0
lines changed

16 files changed

+628
-0
lines changed

board.py

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# -*- encoding: utf-8 -*-
2+
from copy import copy, deepcopy
3+
from itertools import groupby
4+
import pieces
5+
import re
6+
7+
'''
8+
Board
9+
10+
This module implements a singleton chessboard class
11+
12+
TODO:
13+
14+
* PGN export
15+
* En passant
16+
* Castling
17+
* Promoting pawns
18+
* Fifty-move rule
19+
20+
'''
21+
22+
class InvalidCoord(Exception): pass
23+
class InvalidMove(Exception): pass
24+
class Check(Exception): pass
25+
class CheckMate(Exception): pass
26+
class Draw(Exception): pass
27+
class NotYourTurn(Exception): pass
28+
29+
axis_y = ('A','B','C','D','E','F','G','H')
30+
axis_x = tuple(range(1,9)) # (1,2,3,...8)
31+
player_turn = None
32+
captured_pieces = { 'white': [], 'black': [] }
33+
castling = ''
34+
en_passant = ''
35+
halfmove_clock = 0
36+
fullmove_number = 1
37+
history = []
38+
39+
unicode_pieces = {
40+
'R': u'♜', 'N': u'♞', 'B': u'♝', 'Q': u'♛',
41+
'K': u'♚', 'P': u'♟', 'r': u'♖', 'n': u'♘',
42+
'b': u'♗', 'q': u'♕', 'k': u'♔', 'p': u'♙',
43+
None: ' '
44+
}
45+
46+
FEN_starting = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
47+
48+
def init():
49+
load(FEN_starting)
50+
51+
def get(coord):
52+
coord = coord.upper()
53+
regex = re.compile(r"^[A-Z][1-8]$")
54+
if not re.match(regex, coord.upper()): raise KeyError
55+
56+
x,y = number_notation(coord)
57+
return table[x][y]
58+
59+
def move(p1, p2):
60+
p1, p2 = p1.upper(), p2.upper()
61+
piece = get(p1)
62+
dest = get(p2)
63+
global player_turn
64+
if player_turn != piece.color:
65+
raise NotYourTurn("Not " + piece.get_color() + "'s turn!")
66+
67+
if piece.get_color() == "white":
68+
enemy = "black"
69+
else:
70+
enemy = "white"
71+
72+
if p2 not in piece.possible_moves(p1):
73+
raise InvalidMove
74+
75+
enemy_moves = all_possible_moves(enemy)
76+
if enemy_moves:
77+
# Save current state
78+
global table
79+
current_table = deepcopy(table)
80+
81+
if p2 in piece.possible_moves(p1):
82+
_do_move(p1, p2)
83+
elif not piece.possible_moves(p1):
84+
raise CheckMate(enemy + " wins!")
85+
86+
if is_in_check(piece.get_color()):
87+
# Restore table
88+
table = current_table
89+
raise Check
90+
91+
_finish_move(piece,dest,p1,p2)
92+
93+
elif p2 in piece.possible_moves(p1):
94+
_do_move(p1, p2)
95+
_finish_move(piece,dest,p1,p2)
96+
elif not piece.possible_moves(p1):
97+
raise Draw
98+
99+
def get_enemy(color):
100+
if color == "white":
101+
return "black"
102+
else:
103+
return "white"
104+
105+
106+
def _do_move(p1, p2):
107+
''' Move a piece without validation '''
108+
piece = get(p1)
109+
dest = get(p2)
110+
p1c = number_notation(p1)
111+
p2c = number_notation(p2)
112+
table[p1c[0]][p1c[1]] = None
113+
table[p2c[0]][p2c[1]] = piece
114+
115+
def _finish_move(piece, dest,p1,p2):
116+
''' Set next player turn, count moves, log moves '''
117+
global player_turn, fullmove_number, history, halfmove_clock
118+
enemy = get_enemy(piece.get_color())
119+
player_turn = pieces.COLORS[enemy]
120+
if piece.get_color() == 'black':
121+
fullmove_number += 1
122+
halfmove_clock +=1
123+
124+
abbr = piece.abbriviation.upper()
125+
if abbr == 'P':
126+
# Pawn has no letter
127+
abbr = ''
128+
# Pawn resets halfmove_clock
129+
halfmove_clock = 0
130+
if dest is None:
131+
# No capturing
132+
movetext = abbr + p2.lower()
133+
else:
134+
# Capturing
135+
movetext = abbr + 'x' + p2.lower()
136+
# Capturing resets halfmove_clock
137+
halfmove_clock = 0
138+
139+
history.append(movetext)
140+
141+
142+
def all_possible_moves(color):
143+
result = []
144+
for x in range(0,len(table)):
145+
for y in range(0,len(table[x])):
146+
if (table[x][y] is not None) and table[x][y].get_color() == color:
147+
moves = table[x][y].possible_moves(letter_notation((x,y)))
148+
if moves: result += moves
149+
return result
150+
151+
def occupied(color):
152+
result = []
153+
try:
154+
color = pieces.COLORS[color]
155+
except KeyError:
156+
raise pieces.InvalidColor
157+
158+
for x in range(0,len(table)):
159+
for y in range(0,len(table[x])):
160+
if (table[x][y] is not None) and (table[x][y].color == color):
161+
result.append((x, y))
162+
return result
163+
164+
def get_king_position(color):
165+
for x in range(0,len(table)):
166+
for y in range(0,len(table[x])):
167+
piece = table[x][y]
168+
if (piece is not None) and\
169+
isinstance(piece, pieces.King) and\
170+
(piece.color == pieces.COLORS[color]):
171+
return letter_notation((x,y))
172+
173+
def get_king(color):
174+
return get(get_king_position(color))
175+
176+
def is_in_check(color):
177+
king = get_king(color)
178+
if color == 'white': enemy = 'black'
179+
else: enemy = 'white'
180+
return king in map(get, all_possible_moves(enemy))
181+
182+
def number_notation(coord):
183+
return int(coord[1])-1, axis_y.index(coord[0])
184+
185+
def unicode_representation():
186+
for number in axis_x[::-1]:
187+
print " " + str(number) + " ",
188+
for letter in axis_y:
189+
piece = get(letter+str(number))
190+
if piece is not None:
191+
print unicode_pieces[piece.abbriviate()] + ' ',
192+
else: print ' ',
193+
print "\n"
194+
print " " + " ".join(axis_y)
195+
196+
def is_in_bounds(coord):
197+
if coord[1] < 0 or coord[1] > 7 or\
198+
coord[0] < 0 or coord[0] > 7:
199+
return False
200+
else: return True
201+
202+
def save_to_file():
203+
with open("state.fen", "w") as savefile:
204+
savefile.write(export())
205+
206+
def load(fen):
207+
''' Import state from FEN notation '''
208+
global table, castling, en_passant,\
209+
halfmove_clock, fullmove_number, player_turn
210+
# Split data
211+
fen = fen.split(' ')
212+
# Expand blanks
213+
def expand(match): return ' ' * int(match.group(0))
214+
215+
fen[0] = re.compile(r'\d').sub(expand, fen[0])
216+
table = [map(pieces.piece,list(row)) for row in fen[0].split('/')]
217+
table.reverse()
218+
if fen[1] == 'w': player_turn = pieces.COLORS['white']
219+
else: player_turn = pieces.COLORS['black']
220+
castling = fen[2]
221+
en_passant = fen[3]
222+
halfmove_clock = int(fen[4])
223+
fullmove_number = int(fen[5])
224+
225+
def export():
226+
''' Export state to FEN notation '''
227+
def join(k, g):
228+
if k == ' ': return str(len(g))
229+
else: return "".join(g)
230+
231+
def stringify(row):
232+
result = ''
233+
for piece in row:
234+
if piece is not None:
235+
result += piece.abbriviate()
236+
else: result += ' '
237+
# replace spaces with their count
238+
result = [join(k, list(g)) for k,g in groupby(result)]
239+
return "".join(result)
240+
241+
if player_turn == 0: turn = 'w'
242+
else: turn = 'b'
243+
244+
result = "/".join([stringify(row) for row in table][::-1])
245+
result += " " + turn +\
246+
" " + castling +\
247+
" " + en_passant +\
248+
" " + str(halfmove_clock) +\
249+
" " + str(fullmove_number)
250+
return result
251+
252+
def letter_notation(coord):
253+
if not is_in_bounds(coord):
254+
raise InvalidCoord
255+
256+
try:
257+
return axis_y[coord[1]] + str(axis_x[coord[0]])
258+
except IndexError:
259+
raise InvalidCoord

0 commit comments

Comments
 (0)