1
1
# -*- encoding: utf-8 -*-
2
- from copy import copy , deepcopy
3
2
from itertools import groupby
4
3
5
4
import pieces
@@ -13,7 +12,10 @@ class CheckMate(ChessError): pass
13
12
class Draw (ChessError ): pass
14
13
class NotYourTurn (ChessError ): pass
15
14
16
- class Board (object ):
15
+ FEN_STARTING = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
16
+ RANK_REGEX = re .compile (r"^[A-Z][1-8]$" )
17
+
18
+ class Board (dict ):
17
19
'''
18
20
Board
19
21
@@ -38,7 +40,7 @@ class Board(object):
38
40
en_passant = '-'
39
41
halfmove_clock = 0
40
42
fullmove_number = 1
41
- table = {}
43
+
42
44
history = []
43
45
44
46
unicode_pieces = {
@@ -48,41 +50,54 @@ class Board(object):
48
50
None : ' '
49
51
}
50
52
51
- FEN_starting = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
52
-
53
53
def __init__ (self , fen = None ):
54
- if fen is None :
55
- self .load (self .FEN_starting )
56
- else :
57
- self .load (fen )
54
+ if fen is None : self .load (FEN_STARTING )
55
+ else : self .load (fen )
58
56
59
57
def __getitem__ (self , coord ):
60
58
if isinstance (coord , str ):
61
59
coord = coord .upper ()
62
- regex = re .compile (r"^[A-Z][1-8]$" )
63
- if not re .match (regex , coord .upper ()): raise KeyError
60
+ if not re .match (RANK_REGEX , coord .upper ()): raise KeyError
64
61
elif isinstance (coord , tuple ):
65
62
coord = self .letter_notation (coord )
66
-
67
63
try :
68
- return self . table [ coord ]
64
+ return super ( Board , self ). __getitem__ ( coord )
69
65
except KeyError :
70
66
return None
71
67
68
+ def check_for_check_after_move (self , p ):
69
+ # Create a temporary board
70
+ p1 ,p2 = p
71
+ tmp = Board (self .export ())
72
+ tmp ._do_move (p1 ,p2 )
73
+ return self .is_in_check (self [p1 ].color )
74
+
72
75
def move (self , p1 , p2 ):
73
76
p1 , p2 = p1 .upper (), p2 .upper ()
74
77
piece = self [p1 ]
75
78
dest = self [p2 ]
76
79
77
80
if self .player_turn != piece .color :
78
- raise NotYourTurn ("Not " + piece .get_color () + "'s turn!" )
81
+ raise NotYourTurn ("Not " + piece .color + "'s turn!" )
79
82
80
83
enemy = self .get_enemy (piece .color )
84
+ possible_moves = piece .possible_moves (p1 )
81
85
# 0. Check if p2 is in the possible moves
82
- if p2 not in piece .possible_moves (p1 ):
83
- raise InvalidMove
86
+ if p2 not in possible_moves :
87
+ raise InvalidMove
88
+
89
+ # If enemy has any moves look for check
90
+ if self .all_possible_moves (enemy ):
91
+ filter (self .check_for_check_after_move , map (lambda p2 : (p1 ,p2 ), possible_moves ))
92
+
93
+ if not possible_moves and self .is_in_check (piece .color ):
94
+ raise CheckMate
95
+ elif not possible_moves :
96
+ raise Draw
97
+ else :
98
+ self ._do_move (p1 ,p2 )
99
+ self ._finish_move (piece , dest , p1 ,p2 )
84
100
85
- self ._do_move (p1 , p2 )
86
101
'''
87
102
# 1. Filter possible moves: remove the ones that will make you in check
88
103
# if no possible moves and not in check -- Draw
@@ -111,90 +126,102 @@ def move(self, p1, p2):
111
126
'''
112
127
113
128
def get_enemy (self , color ):
114
- if color == "white" :
115
- return "black"
116
- else :
117
- return "white"
129
+ if color == "white" : return "black"
130
+ else : return "white"
118
131
119
132
def _do_move (self , p1 , p2 ):
120
- ''' Move a piece without validation '''
133
+ '''
134
+ Move a piece without validation
135
+ '''
121
136
piece = self [p1 ]
122
137
dest = self [p2 ]
123
- p1c = number_notation (p1 )
124
- p2c = number_notation (p2 )
125
138
del self [p1 ]
126
139
self [p2 ] = piece
127
140
128
141
def _finish_move (self , piece , dest ,p1 ,p2 ):
129
- ''' Set next player turn, count moves, log moves '''
130
- global player_turn , fullmove_number , history , halfmove_clock
131
- enemy = get_enemy ( piece . get_color ())
132
- player_turn = pieces . COLORS [ enemy ]
133
- if piece .get_color () == 'black' :
134
- fullmove_number += 1
135
- halfmove_clock += 1
136
-
137
- abbr = piece .abbriviation . upper ()
142
+ '''
143
+ Set next player turn, count moves, log moves, etc.
144
+ '''
145
+ enemy = self . get_enemy ( piece . color )
146
+ if piece .color == 'black' :
147
+ self . fullmove_number += 1
148
+ self . halfmove_clock += 1
149
+ self . player_turn = enemy
150
+ abbr = piece .abbriviation
138
151
if abbr == 'P' :
139
152
# Pawn has no letter
140
153
abbr = ''
141
154
# Pawn resets halfmove_clock
142
- halfmove_clock = 0
155
+ self . halfmove_clock = 0
143
156
if dest is None :
144
157
# No capturing
145
158
movetext = abbr + p2 .lower ()
146
159
else :
147
160
# Capturing
148
161
movetext = abbr + 'x' + p2 .lower ()
149
162
# Capturing resets halfmove_clock
150
- halfmove_clock = 0
163
+ self . halfmove_clock = 0
151
164
152
- history .append (movetext )
165
+ self . history .append (movetext )
153
166
154
167
155
168
def all_possible_moves (self , color ):
169
+ '''
170
+ Return a list of `color`'s possible moves.
171
+ Does not check for check.
172
+ '''
156
173
if (color not in ("black" , "white" )): raise InvalidColor
157
174
result = []
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 ].get_color () == color :
161
- moves = table [x ][y ].possible_moves (letter_notation ((x ,y )))
162
- if moves : result += moves
175
+ for coord in self .keys ():
176
+ if (self [coord ] is not None ) and self [coord ].color == color :
177
+ moves = self [coord ].possible_moves (coord )
178
+ if moves : result += moves
163
179
return result
164
180
165
181
def occupied (self , color ):
182
+ '''
183
+ Return a list of coordinates occupied by `color`
184
+ '''
166
185
result = []
167
186
if (color not in ("black" , "white" )): raise InvalidColor
168
187
169
- for x in range (0 ,len (table )):
170
- for y in range (0 ,len (table [x ])):
171
- if (table [x ][y ] is not None ) and (table [x ][y ].color == color ):
172
- result .append ((x , y ))
188
+ for coord in self :
189
+ if self [coord ].color == color : result .append (coord )
173
190
return result
174
191
192
+ def is_king (self , piece ):
193
+ return isinstance (piece , pieces .King )
194
+
195
+
175
196
def get_king_position (self , color ):
176
- for x in range (0 ,len (table )):
177
- for y in range (0 ,len (table [x ])):
178
- piece = table [x ][y ]
179
- if (piece is not None ) and \
180
- isinstance (piece , pieces .King ) and \
181
- (piece .color == pieces .COLORS [color ]):
182
- return letter_notation ((x ,y ))
197
+ for pos in self .keys ():
198
+ if self .is_king (self [pos ]) and self [pos ].color == color :
199
+ return pos
183
200
184
201
def get_king (self , color ):
185
202
if (color not in ("black" , "white" )): raise InvalidColor
186
- return get ( get_king_position (color ))
203
+ return self [ self . get_king_position (color )]
187
204
188
205
def is_in_check (self , color ):
189
206
if (color not in ("black" , "white" )): raise InvalidColor
190
207
king = self .get_king (color )
191
208
enemy = self .get_enemy (color )
192
- return king in map (get , all_possible_moves (enemy ))
209
+ return king in map (self .__getitem__ , self .all_possible_moves (enemy ))
210
+
211
+ def letter_notation (self ,coord ):
212
+ if not self .is_in_bounds (coord ): raise InvalidCoord
213
+ try :
214
+ return self .axis_y [coord [1 ]] + str (self .axis_x [coord [0 ]])
215
+ except IndexError :
216
+ raise InvalidCoord
193
217
194
218
def number_notation (self , coord ):
195
- return int (coord [1 ])- 1 , axis_y .index (coord [0 ])
219
+ return int (coord [1 ])- 1 , self . axis_y .index (coord [0 ])
196
220
197
221
def unicode_representation (self ):
222
+ '''
223
+ Print a text-mode chessboard using the unicode chess pieces
224
+ '''
198
225
for number in self .axis_x [::- 1 ]:
199
226
print " " + str (number ) + " " ,
200
227
for letter in self .axis_y :
@@ -212,7 +239,9 @@ def is_in_bounds(self, coord):
212
239
else : return True
213
240
214
241
def load (self , fen ):
215
- ''' Import state from FEN notation '''
242
+ '''
243
+ Import state from FEN notation
244
+ '''
216
245
# Split data
217
246
fen = fen .split (' ' )
218
247
# Expand blanks
@@ -224,8 +253,8 @@ def expand(match): return ' ' * int(match.group(0))
224
253
for y , letter in enumerate (row ):
225
254
if letter == ' ' : continue
226
255
coord = self .letter_notation ((7 - x ,y ))
227
- self . table [coord ] = pieces .piece (letter )
228
- self . table [coord ].place (self )
256
+ self [coord ] = pieces .piece (letter )
257
+ self [coord ].place (self )
229
258
230
259
if fen [1 ] == 'w' : self .player_turn = 'white'
231
260
else : self .player_turn = 'black'
@@ -236,7 +265,9 @@ def expand(match): return ' ' * int(match.group(0))
236
265
self .fullmove_number = int (fen [5 ])
237
266
238
267
def export (self ):
239
- ''' Export state to FEN notation '''
268
+ '''
269
+ Export state to FEN notation
270
+ '''
240
271
def join (k , g ):
241
272
if k == ' ' : return str (len (g ))
242
273
else : return "" .join (g )
@@ -264,12 +295,3 @@ def replace_spaces(row):
264
295
str (self .halfmove_clock ),
265
296
str (self .fullmove_number )]))
266
297
return result
267
-
268
- def letter_notation (self ,coord ):
269
- if not self .is_in_bounds (coord ):
270
- raise InvalidCoord
271
-
272
- try :
273
- return self .axis_y [coord [1 ]] + str (self .axis_x [coord [0 ]])
274
- except IndexError :
275
- raise InvalidCoord
0 commit comments