3 # Dictionary that stores the unicode character representations of the different pieces
4 # Chess was clearly the reason why unicode was invented
5 # For some reason none of the pygame chess implementations I found used them!
6 piece_char = {"white" : {"king" : u'\u2654',
13 "black" : {"king" : u'\u265A',
21 images = {"white" : {}, "black" : {}}
22 small_images = {"white" : {}, "black" : {}}
24 # A thread to make things pretty
25 class GraphicsThread(StoppableThread):
26 def __init__(self, board, title = "UCC::Progcomp 2013 - QChess", grid_sz = [80,80]):
27 StoppableThread.__init__(self)
31 self.window = pygame.display.set_mode((grid_sz[0] * w, grid_sz[1] * h))
32 pygame.display.set_caption(title)
33 self.grid_sz = grid_sz[:]
34 self.state = {"select" : None, "dest" : None, "moves" : None, "overlay" : None, "coverage" : None}
36 self.lock = threading.RLock()
37 self.cond = threading.Condition()
40 l_size = 5*(self.grid_sz[0] / 8)
41 s_size = 3*(self.grid_sz[0] / 8)
42 for p in piece_types.keys():
44 images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", l_size).render(piece_char[c][p], True,(0,0,0))})
45 small_images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", s_size).render(piece_char[c][p],True,(0,0,0))})
48 images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", l_size+1).render(piece_char["black"][p], True,(255,255,255))})
49 images[c][p].blit(pygame.font.Font("data/DejaVuSans.ttf", l_size).render(piece_char[c][p], True,(0,0,0)),(0,0))
50 small_images[c].update({p : pygame.font.Font("data/DejaVuSans.ttf", s_size+1).render(piece_char["black"][p],True,(255,255,255))})
51 small_images[c][p].blit(pygame.font.Font("data/DejaVuSans.ttf", s_size).render(piece_char[c][p],True,(0,0,0)),(0,0))
57 # On the run from the world
60 while not self.stopped():
62 self.board.display_grid(window = self.window, grid_sz = self.grid_sz) # Draw the board
66 self.board.display_pieces(window = self.window, grid_sz = self.grid_sz) # Draw the board
70 for event in pygame.event.get():
71 if event.type == pygame.QUIT:
72 if isinstance(game, GameThread):
74 game.final_result = ""
75 if game.state["turn"] != None:
76 game.final_result = game.state["turn"].colour + " "
77 game.final_result += "terminated"
81 elif event.type == pygame.MOUSEBUTTONDOWN:
82 self.mouse_down(event)
83 elif event.type == pygame.MOUSEBUTTONUP:
91 self.message("Game ends, result \""+str(game.final_result) + "\"")
94 # Wake up anyone who is sleeping
99 pygame.quit() # Time to say goodbye
101 # Mouse release event handler
102 def mouse_up(self, event):
103 if event.button == 3:
105 self.state["overlay"] = None
106 elif event.button == 2:
108 self.state["coverage"] = None
110 # Mouse click event handler
111 def mouse_down(self, event):
112 if event.button == 1:
113 m = [event.pos[i] / self.grid_sz[i] for i in range(2)]
114 if isinstance(game, GameThread):
116 p = game.state["turn"]
121 if isinstance(p, HumanPlayer):
123 s = self.board.grid[m[0]][m[1]]
124 select = self.state["select"]
126 if s != None and s.colour != p.colour:
127 self.message("Wrong colour") # Look at all this user friendliness!
130 # Notify human player of move
133 self.state["select"] = s
134 self.state["dest"] = None
143 if self.state["moves"] == None:
146 if not m in self.state["moves"]:
147 self.message("Illegal Move") # I still think last year's mouse interface was adequate
152 if self.state["dest"] == None:
154 self.state["dest"] = m
155 self.state["select"] = None
156 self.state["moves"] = None
159 elif event.button == 3:
160 m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))]
161 if isinstance(game, GameThread):
163 p = game.state["turn"]
168 if isinstance(p, HumanPlayer):
170 self.state["overlay"] = self.board.probability_grid(self.board.grid[m[0]][m[1]])
172 elif event.button == 2:
173 m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))]
174 if isinstance(game, GameThread):
176 p = game.state["turn"]
181 if isinstance(p, HumanPlayer):
183 self.state["coverage"] = self.board.coverage(m[0], m[1], None, self.state["select"])
188 square_img = pygame.Surface((self.grid_sz[0], self.grid_sz[1]),pygame.SRCALPHA) # A square image
189 # Draw square over the selected piece
191 select = self.state["select"]
193 mp = [self.grid_sz[i] * [select.x, select.y][i] for i in range(len(self.grid_sz))]
194 square_img.fill(pygame.Color(0,255,0,64))
195 self.window.blit(square_img, mp)
196 # If a piece is selected, draw all reachable squares
197 # (This quality user interface has been patented)
199 m = self.state["moves"]
201 square_img.fill(pygame.Color(255,0,0,128)) # Draw them in blood red
203 mp = [self.grid_sz[i] * move[i] for i in range(2)]
204 self.window.blit(square_img, mp)
205 # If a piece is overlayed, show all squares that it has a probability to reach
207 m = self.state["overlay"]
212 mp = [self.grid_sz[i] * [x,y][i] for i in range(2)]
213 square_img.fill(pygame.Color(255,0,255,int(m[x][y] * 128))) # Draw in purple
214 self.window.blit(square_img, mp)
215 font = pygame.font.Font(None, 14)
216 text = font.render("{0:.2f}".format(round(m[x][y],2)), 1, pygame.Color(0,0,0))
217 self.window.blit(text, mp)
219 # If a square is selected, highlight all pieces that have a probability to reach it
221 m = self.state["coverage"]
224 mp = [self.grid_sz[i] * [p.x,p.y][i] for i in range(2)]
225 square_img.fill(pygame.Color(0,255,255, int(m[p] * 196))) # Draw in pale blue
226 self.window.blit(square_img, mp)
227 font = pygame.font.Font(None, 14)
228 text = font.render("{0:.2f}".format(round(m[p],2)), 1, pygame.Color(0,0,0))
229 self.window.blit(text, mp)
230 # Draw a square where the mouse is
231 # This also serves to indicate who's turn it is
233 if isinstance(game, GameThread):
235 turn = game.state["turn"]
239 if isinstance(turn, HumanPlayer):
240 mp = [self.grid_sz[i] * int(pygame.mouse.get_pos()[i] / self.grid_sz[i]) for i in range(2)]
241 square_img.fill(pygame.Color(0,0,255,128))
242 if turn.colour == "white":
243 c = pygame.Color(255,255,255)
245 c = pygame.Color(0,0,0)
246 pygame.draw.rect(square_img, c, (0,0,self.grid_sz[0], self.grid_sz[1]), self.grid_sz[0]/10)
247 self.window.blit(square_img, mp)
249 # Message in a bottle
250 def message(self, string, pos = None, colour = None, font_size = 32):
251 font = pygame.font.Font(None, font_size)
253 colour = pygame.Color(0,0,0)
255 text = font.render(string, 1, colour)
258 s = pygame.Surface((text.get_width(), text.get_height()), pygame.SRCALPHA)
259 s.fill(pygame.Color(128,128,128))
261 tmp = self.window.get_size()
264 pos = (tmp[0] / 2 - text.get_width() / 2, tmp[1] / 3 - text.get_height())
266 pos = (pos[0]*text.get_width() + tmp[0] / 2 - text.get_width() / 2, pos[1]*text.get_height() + tmp[1] / 3 - text.get_height())
269 rect = (pos[0], pos[1], text.get_width(), text.get_height())
271 pygame.draw.rect(self.window, pygame.Color(0,0,0), pygame.Rect(rect), 1)
272 self.window.blit(s, pos)
273 self.window.blit(text, pos)
275 pygame.display.flip()
277 def getstr(self, prompt = None):
278 s = pygame.Surface((self.window.get_width(), self.window.get_height()))
279 s.blit(self.window, (0,0))
286 self.message(result, pos = (0, 1))
289 for event in pygame.event.get():
290 if event.type == pygame.QUIT:
292 if event.type == pygame.KEYDOWN:
293 if event.key == pygame.K_BACKSPACE:
294 result = result[0:len(result)-1]
295 self.window.blit(s, (0,0)) # Revert the display
300 if event.unicode == '\r':
303 result += str(event.unicode)
308 # Function to pick a button
309 def SelectButton(self, choices, prompt = None, font_size=32):
310 self.board.display_grid(self.window, self.grid_sz)
313 font = pygame.font.Font(None, font_size)
315 sz = self.window.get_size()
318 for i in range(len(choices)):
321 text = font.render(c, 1, pygame.Color(0,0,0))
322 p = (sz[0] / 2 - (1.5*text.get_width())/2, sz[1] / 2 +(i-1)*text.get_height()+(i*2))
323 targets.append((p[0], p[1], p[0] + 1.5*text.get_width(), p[1] + text.get_height()))
326 mp =pygame.mouse.get_pos()
327 for i in range(len(choices)):
329 if mp[0] > targets[i][0] and mp[0] < targets[i][2] and mp[1] > targets[i][1] and mp[1] < targets[i][3]:
330 font_colour = pygame.Color(255,0,0)
331 box_colour = pygame.Color(0,0,255,128)
333 font_colour = pygame.Color(0,0,0)
334 box_colour = pygame.Color(128,128,128)
336 text = font.render(c, 1, font_colour)
337 s = pygame.Surface((text.get_width()*1.5, text.get_height()), pygame.SRCALPHA)
339 pygame.draw.rect(s, (0,0,0), (0,0,1.5*text.get_width(), text.get_height()), self.grid_sz[0]/10)
340 s.blit(text, ((text.get_width()*1.5)/2 - text.get_width()/2 ,0))
341 self.window.blit(s, targets[i][0:2])
344 pygame.display.flip()
346 for event in pygame.event.get():
347 if event.type == pygame.QUIT:
349 elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
350 for i in range(len(targets)):
352 if event.pos[0] > t[0] and event.pos[0] < t[2]:
353 if event.pos[1] > t[1] and event.pos[1] < t[3]:
355 #print "Reject " + str(i) + str(event.pos) + " vs " + str(t)
358 # Function to pick players in a nice GUI way
359 def SelectPlayers(self, players = []):
363 missing = ["white", "black"]
365 missing.remove(p.colour)
367 for colour in missing:
370 choice = self.SelectButton(["human", "agent", "network"],prompt = "Choose " + str(colour) + " player", font_size=32)
372 players.append(HumanPlayer("human", colour))
376 from tkFileDialog import askopenfilename
377 root = Tkinter.Tk() # Need a root to make Tkinter behave
378 root.withdraw() # Some sort of magic incantation
379 path = askopenfilename(parent=root, initialdir="../agents",title=
382 return self.SelectPlayers()
383 players.append(make_player(path, colour))
385 print "Exception was " + str(e.message)
388 self.board.display_grid(self.window, self.grid_sz)
389 pygame.display.flip()
390 path = self.getstr(prompt = "Enter path:")
395 return self.SelectPlayers()
398 p = make_player(path, colour)
400 self.board.display_grid(self.window, self.grid_sz)
401 pygame.display.flip()
402 self.message("Invalid path!")
409 self.board.display_grid(self.window, self.grid_sz)
411 address = self.getstr(prompt = "Address? (leave blank for server)")
418 map(int, address.split("."))
420 self.board.display_grid(self.window, self.grid_sz)
421 self.message("Invalid IPv4 address!")
424 players.append(NetworkReceiver(colour, address))
427 #print str(self) + ".SelectPlayers returns " + str(players)