Lots of stuff happened
[progcomp2013.git] / qchess / graphics.py
1 import pygame
2
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',
7                          "queen" : u'\u2655',
8                          "rook" : u'\u2656',
9                          "bishop" : u'\u2657',
10                          "knight" : u'\u2658',
11                          "pawn" : u'\u2659',
12                          "unknown" : '?'},
13                 "black" : {"king" : u'\u265A',
14                          "queen" : u'\u265B',
15                          "rook" : u'\u265C',
16                          "bishop" : u'\u265D',
17                          "knight" : u'\u265E',
18                          "pawn" : u'\u265F',
19                          "unknown" : '?'}}
20
21 images = {"white" : {}, "black" : {}}
22 small_images = {"white" : {}, "black" : {}}
23
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)
28                 
29                 self.board = board
30                 pygame.init()
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}
35                 self.error = 0
36                 self.lock = threading.RLock()
37                 self.cond = threading.Condition()
38
39                 # Get the font sizes
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():
43                         c = "black"
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))})
46                         c = "white"
47
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))
52
53                 
54         
55
56
57         # On the run from the world
58         def run(self):
59                 
60                 while not self.stopped():
61                         
62                         self.board.display_grid(window = self.window, grid_sz = self.grid_sz) # Draw the board
63
64                         self.overlay()
65
66                         self.board.display_pieces(window = self.window, grid_sz = self.grid_sz) # Draw the board                
67
68                         pygame.display.flip()
69
70                         for event in pygame.event.get():
71                                 if event.type == pygame.QUIT:
72                                         if isinstance(game, GameThread):
73                                                 with game.lock:
74                                                         game.final_result = ""
75                                                         if game.state["turn"] != None:
76                                                                 game.final_result = game.state["turn"].colour + " "
77                                                         game.final_result += "terminated"
78                                                 game.stop()
79                                         self.stop()
80                                         break
81                                 elif event.type == pygame.MOUSEBUTTONDOWN:
82                                         self.mouse_down(event)
83                                 elif event.type == pygame.MOUSEBUTTONUP:
84                                         self.mouse_up(event)
85                                         
86
87                                 
88                                                                 
89                                                 
90                                                 
91                 self.message("Game ends, result \""+str(game.final_result) + "\"")
92                 time.sleep(1)
93
94                 # Wake up anyone who is sleeping
95                 self.cond.acquire()
96                 self.cond.notify()
97                 self.cond.release()
98
99                 pygame.quit() # Time to say goodbye
100
101         # Mouse release event handler
102         def mouse_up(self, event):
103                 if event.button == 3:
104                         with self.lock:
105                                 self.state["overlay"] = None
106                 elif event.button == 2:
107                         with self.lock:
108                                 self.state["coverage"] = None   
109
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):
115                                 with game.lock:
116                                         p = game.state["turn"]
117                         else:
118                                         p = None
119                                         
120                                         
121                         if isinstance(p, HumanPlayer):
122                                 with self.lock:
123                                         s = self.board.grid[m[0]][m[1]]
124                                         select = self.state["select"]
125                                 if select == None:
126                                         if s != None and s.colour != p.colour:
127                                                 self.message("Wrong colour") # Look at all this user friendliness!
128                                                 time.sleep(1)
129                                                 return
130                                         # Notify human player of move
131                                         self.cond.acquire()
132                                         with self.lock:
133                                                 self.state["select"] = s
134                                                 self.state["dest"] = None
135                                         self.cond.notify()
136                                         self.cond.release()
137                                         return
138
139                                 if select == None:
140                                         return
141                                                 
142                                         
143                                 if self.state["moves"] == None:
144                                         return
145
146                                 if not m in self.state["moves"]:
147                                         self.message("Illegal Move") # I still think last year's mouse interface was adequate
148                                         time.sleep(2)
149                                         return
150                                                 
151                                 with self.lock:
152                                         if self.state["dest"] == None:
153                                                 self.cond.acquire()
154                                                 self.state["dest"] = m
155                                                 self.state["select"] = None
156                                                 self.state["moves"] = None
157                                                 self.cond.notify()
158                                                 self.cond.release()
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):
162                                 with game.lock:
163                                         p = game.state["turn"]
164                         else:
165                                 p = None
166                                         
167                                         
168                         if isinstance(p, HumanPlayer):
169                                 with self.lock:
170                                         self.state["overlay"] = self.board.probability_grid(self.board.grid[m[0]][m[1]])
171
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):
175                                 with game.lock:
176                                         p = game.state["turn"]
177                         else:
178                                 p = None
179                         
180                         
181                         if isinstance(p, HumanPlayer):
182                                 with self.lock:
183                                         self.state["coverage"] = self.board.coverage(m[0], m[1], None, self.state["select"])
184                                 
185         # Draw the overlay
186         def overlay(self):
187
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
190                 with self.lock:
191                         select = self.state["select"]
192                 if select != None:
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)
198                 with self.lock:
199                         m = self.state["moves"]
200                 if m != None:
201                         square_img.fill(pygame.Color(255,0,0,128)) # Draw them in blood red
202                         for move in m:
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
206                 with self.lock:
207                         m = self.state["overlay"]
208                 if m != None:
209                         for x in range(w):
210                                 for y in range(h):
211                                         if m[x][y] > 0.0:
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)
218                                 
219                 # If a square is selected, highlight all pieces that have a probability to reach it
220                 with self.lock:                         
221                         m = self.state["coverage"]
222                 if m != None:
223                         for p in m:
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
232                 
233                 if isinstance(game, GameThread):
234                         with game.lock:
235                                 turn = game.state["turn"]
236                 else:
237                         turn = None
238
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)
244                         else:
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)
248
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)
252                 if colour == None:
253                         colour = pygame.Color(0,0,0)
254                 
255                 text = font.render(string, 1, colour)
256         
257
258                 s = pygame.Surface((text.get_width(), text.get_height()), pygame.SRCALPHA)
259                 s.fill(pygame.Color(128,128,128))
260
261                 tmp = self.window.get_size()
262
263                 if pos == None:
264                         pos = (tmp[0] / 2 - text.get_width() / 2, tmp[1] / 3 - text.get_height())
265                 else:
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())
267                 
268
269                 rect = (pos[0], pos[1], text.get_width(), text.get_height())
270         
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)
274
275                 pygame.display.flip()
276
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))
280                 result = ""
281
282                 while True:
283                         #print "LOOP"
284                         if prompt != None:
285                                 self.message(prompt)
286                                 self.message(result, pos = (0, 1))
287         
288                         pygame.event.pump()
289                         for event in pygame.event.get():
290                                 if event.type == pygame.QUIT:
291                                         return None
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
296                                                 continue
297                                 
298                                                 
299                                         try:
300                                                 if event.unicode == '\r':
301                                                         return result
302                                         
303                                                 result += str(event.unicode)
304                                         except:
305                                                 continue
306
307
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)
311                 if prompt != None:
312                         self.message(prompt)
313                 font = pygame.font.Font(None, font_size)
314                 targets = []
315                 sz = self.window.get_size()
316
317                 
318                 for i in range(len(choices)):
319                         c = choices[i]
320                         
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()))
324
325                 while True:
326                         mp =pygame.mouse.get_pos()
327                         for i in range(len(choices)):
328                                 c = choices[i]
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)
332                                 else:
333                                         font_colour = pygame.Color(0,0,0)
334                                         box_colour = pygame.Color(128,128,128)
335                                 
336                                 text = font.render(c, 1, font_colour)
337                                 s = pygame.Surface((text.get_width()*1.5, text.get_height()), pygame.SRCALPHA)
338                                 s.fill(box_colour)
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])
342                                 
343         
344                         pygame.display.flip()
345
346                         for event in pygame.event.get():
347                                 if event.type == pygame.QUIT:
348                                         return None
349                                 elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
350                                         for i in range(len(targets)):
351                                                 t = targets[i]
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]:
354                                                                 return i
355                                                 #print "Reject " + str(i) + str(event.pos) + " vs " + str(t)
356                 
357
358         # Function to pick players in a nice GUI way
359         def SelectPlayers(self, players = []):
360
361
362                 
363                 missing = ["white", "black"]
364                 for p in players:
365                         missing.remove(p.colour)
366
367                 for colour in missing:
368                         
369                         
370                         choice = self.SelectButton(["human", "agent", "network"],prompt = "Choose " + str(colour) + " player", font_size=32)
371                         if choice == 0:
372                                 players.append(HumanPlayer("human", colour))
373                         elif choice == 1:
374                                 try:
375                                         import Tkinter
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=
380 'Choose an agent.')
381                                         if path == "":
382                                                 return self.SelectPlayers()
383                                         players.append(make_player(path, colour))       
384                                 except Exception,e:
385                                         print "Exception was " + str(e.message)
386                                         p = None
387                                         while p == None:
388                                                 self.board.display_grid(self.window, self.grid_sz)
389                                                 pygame.display.flip()
390                                                 path = self.getstr(prompt = "Enter path:")
391                                                 if path == None:
392                                                         return None
393
394                                                 if path == "":
395                                                         return self.SelectPlayers()
396
397                                                 try:
398                                                         p = make_player(path, colour)
399                                                 except:
400                                                         self.board.display_grid(self.window, self.grid_sz)
401                                                         pygame.display.flip()
402                                                         self.message("Invalid path!")
403                                                         time.sleep(1)
404                                                         p = None
405                                         players.append(p)
406                         elif choice == 2:
407                                 address = ""
408                                 while address == "":
409                                         self.board.display_grid(self.window, self.grid_sz)
410                                         
411                                         address = self.getstr(prompt = "Address? (leave blank for server)")
412                                         if address == None:
413                                                 return None
414                                         if address == "":
415                                                 address = None
416                                                 continue
417                                         try:
418                                                 map(int, address.split("."))
419                                         except:
420                                                 self.board.display_grid(self.window, self.grid_sz)
421                                                 self.message("Invalid IPv4 address!")
422                                                 address = ""
423
424                                 players.append(NetworkReceiver(colour, address))
425                         else:
426                                 return None
427                 #print str(self) + ".SelectPlayers returns " + str(players)
428                 return players
429                         
430                                 
431                         

UCC git Repository :: git.ucc.asn.au