c068900e64b5d6dbfc517765a7c84f2dd787de64
[progcomp2013.git] / qchess / src / graphics.py
1 import pygame
2
3
4
5
6
7
8
9
10
11 # A thread to make things pretty
12 class GraphicsThread(StoppableThread):
13         def __init__(self, board, title = "UCC::Progcomp 2013 - QChess", grid_sz = [80,80]):
14                 StoppableThread.__init__(self)
15                 
16                 self.board = board
17                 pygame.init()
18                 self.window = pygame.display.set_mode((grid_sz[0] * w, grid_sz[1] * h))
19                 pygame.display.set_caption(title)
20
21                 #print "Initialised properly"
22                 
23                 self.grid_sz = grid_sz[:]
24                 self.state = {"select" : None, "dest" : None, "moves" : None, "overlay" : None, "coverage" : None}
25                 self.error = 0
26                 self.lock = threading.RLock()
27                 self.cond = threading.Condition()
28
29                 #print "Test font"
30                 pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 32).render("Hello", True,(0,0,0))
31
32                 #load_images()
33                 create_images(grid_sz)
34
35                 """
36                 for c in images.keys():
37                         for p in images[c].keys():
38                                 images[c][p] = images[c][p].convert(self.window)
39                                 small_images[c][p] = small_images[c][p].convert(self.window)
40                 """
41
42                 
43         
44
45
46         # On the run from the world
47         def run(self):
48                 
49                 while not self.stopped():
50                         
51                         #print "Display grid"
52                         self.board.display_grid(window = self.window, grid_sz = self.grid_sz) # Draw the board
53
54                         #print "Display overlay"
55                         self.overlay()
56
57                         #print "Display pieces"
58                         self.board.display_pieces(window = self.window, grid_sz = self.grid_sz) # Draw the board                
59
60                         pygame.display.flip()
61
62                         for event in pygame.event.get():
63                                 if event.type == pygame.QUIT:
64                                         if isinstance(game, GameThread):
65                                                 with game.lock:
66                                                         game.final_result = ""
67                                                         if game.state["turn"] != None:
68                                                                 game.final_result = game.state["turn"].colour + " "
69                                                         game.final_result += "terminated"
70                                                 game.stop()
71                                         self.stop()
72                                         break
73                                 elif event.type == pygame.MOUSEBUTTONDOWN:
74                                         self.mouse_down(event)
75                                 elif event.type == pygame.MOUSEBUTTONUP:
76                                         self.mouse_up(event)
77                                         
78
79                                 
80                                                                 
81                                                 
82                                                 
83                 self.message("Game ends, result \""+str(game.final_result) + "\"")
84                 time.sleep(1)
85
86                 # Wake up anyone who is sleeping
87                 self.cond.acquire()
88                 self.cond.notify()
89                 self.cond.release()
90
91                 pygame.quit() # Time to say goodbye
92
93         # Mouse release event handler
94         def mouse_up(self, event):
95                 if event.button == 3:
96                         with self.lock:
97                                 self.state["overlay"] = None
98                 elif event.button == 2:
99                         with self.lock:
100                                 self.state["coverage"] = None   
101
102         # Mouse click event handler
103         def mouse_down(self, event):
104                 if event.button == 1:
105                         m = [event.pos[i] / self.grid_sz[i] for i in range(2)]
106                         if isinstance(game, GameThread):
107                                 with game.lock:
108                                         p = game.state["turn"]
109                         else:
110                                         p = None
111                                         
112                                         
113                         if isinstance(p, HumanPlayer):
114                                 with self.lock:
115                                         s = self.board.grid[m[0]][m[1]]
116                                         select = self.state["select"]
117                                 if select == None:
118                                         if s != None and s.colour != p.colour:
119                                                 self.message("Wrong colour") # Look at all this user friendliness!
120                                                 time.sleep(1)
121                                                 return
122                                         # Notify human player of move
123                                         self.cond.acquire()
124                                         with self.lock:
125                                                 self.state["select"] = s
126                                                 self.state["dest"] = None
127                                         self.cond.notify()
128                                         self.cond.release()
129                                         return
130
131                                 if select == None:
132                                         return
133                                                 
134                                         
135                                 if self.state["moves"] == None:
136                                         return
137
138                                 if not m in self.state["moves"]:
139                                         self.message("Illegal Move") # I still think last year's mouse interface was adequate
140                                         time.sleep(2)
141                                         return
142                                                 
143                                 with self.lock:
144                                         if self.state["dest"] == None:
145                                                 self.cond.acquire()
146                                                 self.state["dest"] = m
147                                                 self.state["select"] = None
148                                                 self.state["moves"] = None
149                                                 self.cond.notify()
150                                                 self.cond.release()
151                 elif event.button == 3:
152                         m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))]
153                         if isinstance(game, GameThread):
154                                 with game.lock:
155                                         p = game.state["turn"]
156                         else:
157                                 p = None
158                                         
159                                         
160                         if isinstance(p, HumanPlayer):
161                                 with self.lock:
162                                         self.state["overlay"] = self.board.probability_grid(self.board.grid[m[0]][m[1]])
163
164                 elif event.button == 2:
165                         m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))]
166                         if isinstance(game, GameThread):
167                                 with game.lock:
168                                         p = game.state["turn"]
169                         else:
170                                 p = None
171                         
172                         
173                         if isinstance(p, HumanPlayer):
174                                 with self.lock:
175                                         self.state["coverage"] = self.board.coverage(m[0], m[1], None, self.state["select"])
176                                 
177         # Draw the overlay
178         def overlay(self):
179
180                 square_img = pygame.Surface((self.grid_sz[0], self.grid_sz[1]),pygame.SRCALPHA) # A square image
181                 # Draw square over the selected piece
182                 with self.lock:
183                         select = self.state["select"]
184                 if select != None:
185                         mp = [self.grid_sz[i] * [select.x, select.y][i] for i in range(len(self.grid_sz))]
186                         square_img.fill(pygame.Color(0,255,0,64))
187                         self.window.blit(square_img, mp)
188                 # If a piece is selected, draw all reachable squares
189                 # (This quality user interface has been patented)
190                 with self.lock:
191                         m = self.state["moves"]
192                 if m != None:
193                         square_img.fill(pygame.Color(255,0,0,128)) # Draw them in blood red
194                         for move in m:
195                                 mp = [self.grid_sz[i] * move[i] for i in range(2)]
196                                 self.window.blit(square_img, mp)
197                 # If a piece is overlayed, show all squares that it has a probability to reach
198                 with self.lock:
199                         m = self.state["overlay"]
200                 if m != None:
201                         for x in range(w):
202                                 for y in range(h):
203                                         if m[x][y] > 0.0:
204                                                 mp = [self.grid_sz[i] * [x,y][i] for i in range(2)]
205                                                 square_img.fill(pygame.Color(255,0,255,int(m[x][y] * 128))) # Draw in purple
206                                                 self.window.blit(square_img, mp)
207                                                 font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14)
208                                                 text = font.render("{0:.2f}".format(round(m[x][y],2)), 1, pygame.Color(0,0,0))
209                                                 self.window.blit(text, mp)
210                                 
211                 # If a square is selected, highlight all pieces that have a probability to reach it
212                 with self.lock:                         
213                         m = self.state["coverage"]
214                 if m != None:
215                         for p in m:
216                                 mp = [self.grid_sz[i] * [p.x,p.y][i] for i in range(2)]
217                                 square_img.fill(pygame.Color(0,255,255, int(m[p] * 196))) # Draw in pale blue
218                                 self.window.blit(square_img, mp)
219                                 font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14)
220                                 text = font.render("{0:.2f}".format(round(m[p],2)), 1, pygame.Color(0,0,0))
221                                 self.window.blit(text, mp)
222                         # Draw a square where the mouse is
223                 # This also serves to indicate who's turn it is
224                 
225                 if isinstance(game, GameThread):
226                         with game.lock:
227                                 turn = game.state["turn"]
228                 else:
229                         turn = None
230
231                 if isinstance(turn, HumanPlayer):
232                         mp = [self.grid_sz[i] * int(pygame.mouse.get_pos()[i] / self.grid_sz[i]) for i in range(2)]
233                         square_img.fill(pygame.Color(0,0,255,128))
234                         if turn.colour == "white":
235                                 c = pygame.Color(255,255,255)
236                         else:
237                                 c = pygame.Color(0,0,0)
238                         pygame.draw.rect(square_img, c, (0,0,self.grid_sz[0], self.grid_sz[1]), self.grid_sz[0]/10)
239                         self.window.blit(square_img, mp)
240
241         # Message in a bottle
242         def message(self, string, pos = None, colour = None, font_size = 20):
243                 #print "Drawing message..."
244                 font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size)
245                 if colour == None:
246                         colour = pygame.Color(0,0,0)
247                 
248                 text = font.render(string, 1, colour)
249         
250
251                 s = pygame.Surface((text.get_width(), text.get_height()), pygame.SRCALPHA)
252                 s.fill(pygame.Color(128,128,128))
253
254                 tmp = self.window.get_size()
255
256                 if pos == None:
257                         pos = (tmp[0] / 2 - text.get_width() / 2, tmp[1] / 3 - text.get_height())
258                 else:
259                         pos = (pos[0]*text.get_width() + tmp[0] / 2 - text.get_width() / 2, pos[1]*text.get_height() + tmp[1] / 3 - text.get_height())
260                 
261
262                 rect = (pos[0], pos[1], text.get_width(), text.get_height())
263         
264                 pygame.draw.rect(self.window, pygame.Color(0,0,0), pygame.Rect(rect), 1)
265                 self.window.blit(s, pos)
266                 self.window.blit(text, pos)
267
268                 pygame.display.flip()
269
270         def getstr(self, prompt = None):
271                 s = pygame.Surface((self.window.get_width(), self.window.get_height()))
272                 s.blit(self.window, (0,0))
273                 result = ""
274
275                 while True:
276                         #print "LOOP"
277                         if prompt != None:
278                                 self.message(prompt)
279                                 self.message(result, pos = (0, 1))
280         
281                         pygame.event.pump()
282                         for event in pygame.event.get():
283                                 if event.type == pygame.QUIT:
284                                         return None
285                                 if event.type == pygame.KEYDOWN:
286                                         if event.key == pygame.K_BACKSPACE:
287                                                 result = result[0:len(result)-1]
288                                                 self.window.blit(s, (0,0)) # Revert the display
289                                                 continue
290                                 
291                                                 
292                                         try:
293                                                 if event.unicode == '\r':
294                                                         return result
295                                         
296                                                 result += str(event.unicode)
297                                         except:
298                                                 continue
299
300
301         # Function to pick a button
302         def SelectButton(self, choices, prompt = None, font_size=20):
303
304                 #print "Select button called!"
305                 self.board.display_grid(self.window, self.grid_sz)
306                 if prompt != None:
307                         self.message(prompt)
308                 font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size)
309                 targets = []
310                 sz = self.window.get_size()
311
312                 
313                 for i in range(len(choices)):
314                         c = choices[i]
315                         
316                         text = font.render(c, 1, pygame.Color(0,0,0))
317                         p = (sz[0] / 2 - (1.5*text.get_width())/2, sz[1] / 2 +(i-1)*text.get_height()+(i*2))
318                         targets.append((p[0], p[1], p[0] + 1.5*text.get_width(), p[1] + text.get_height()))
319
320                 while True:
321                         mp =pygame.mouse.get_pos()
322                         for i in range(len(choices)):
323                                 c = choices[i]
324                                 if mp[0] > targets[i][0] and mp[0] < targets[i][2] and mp[1] > targets[i][1] and mp[1] < targets[i][3]:
325                                         font_colour = pygame.Color(255,0,0)
326                                         box_colour = pygame.Color(0,0,255,128)
327                                 else:
328                                         font_colour = pygame.Color(0,0,0)
329                                         box_colour = pygame.Color(128,128,128)
330                                 
331                                 text = font.render(c, 1, font_colour)
332                                 s = pygame.Surface((text.get_width()*1.5, text.get_height()), pygame.SRCALPHA)
333                                 s.fill(box_colour)
334                                 pygame.draw.rect(s, (0,0,0), (0,0,1.5*text.get_width(), text.get_height()), self.grid_sz[0]/10)
335                                 s.blit(text, ((text.get_width()*1.5)/2 - text.get_width()/2 ,0))
336                                 self.window.blit(s, targets[i][0:2])
337                                 
338         
339                         pygame.display.flip()
340
341                         for event in pygame.event.get():
342                                 if event.type == pygame.QUIT:
343                                         return None
344                                 elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
345                                         for i in range(len(targets)):
346                                                 t = targets[i]
347                                                 if event.pos[0] > t[0] and event.pos[0] < t[2]:
348                                                         if event.pos[1] > t[1] and event.pos[1] < t[3]:
349                                                                 return i
350                                                 #print "Reject " + str(i) + str(event.pos) + " vs " + str(t)
351                 
352
353         # Function to pick players in a nice GUI way
354         def SelectPlayers(self, players = []):
355
356
357                 #print "SelectPlayers called"
358                 
359                 missing = ["white", "black"]
360                 for p in players:
361                         missing.remove(p.colour)
362
363                 for colour in missing:
364                         
365                         
366                         choice = self.SelectButton(["human", "agent", "network"],prompt = "Choose " + str(colour) + " player")
367                         if choice == 0:
368                                 players.append(HumanPlayer("human", colour))
369                         elif choice == 1:
370                                 if True:
371                                         import Tkinter
372                                         from tkFileDialog import askopenfilename
373                                         root = Tkinter.Tk() # Need a root to make Tkinter behave
374                                         root.withdraw() # Some sort of magic incantation
375                                         path = askopenfilename(parent=root, initialdir="../agents",title=
376 'Choose an agent.')
377                                         if path == "":
378                                                 return self.SelectPlayers()
379                                         players.append(make_player(path, colour))       
380                                 else:
381                                         print "Exception was " + str(e.message)
382                                         p = None
383                                         while p == None:
384                                                 self.board.display_grid(self.window, self.grid_sz)
385                                                 pygame.display.flip()
386                                                 path = self.getstr(prompt = "Enter path:")
387                                                 if path == None:
388                                                         return None
389
390                                                 if path == "":
391                                                         return self.SelectPlayers()
392
393                                                 try:
394                                                         p = make_player(path, colour)
395                                                 except:
396                                                         self.board.display_grid(self.window, self.grid_sz)
397                                                         pygame.display.flip()
398                                                         self.message("Invalid path!")
399                                                         time.sleep(1)
400                                                         p = None
401                                         players.append(p)
402                         elif choice == 2:
403                                 address = ""
404                                 while address == "":
405                                         self.board.display_grid(self.window, self.grid_sz)
406                                         
407                                         address = self.getstr(prompt = "Address? (leave blank for server)")
408                                         if address == None:
409                                                 return None
410                                         if address == "":
411                                                 address = None
412                                                 continue
413                                         try:
414                                                 map(int, address.split("."))
415                                         except:
416                                                 self.board.display_grid(self.window, self.grid_sz)
417                                                 self.message("Invalid IPv4 address!")
418                                                 address = ""
419
420                                 players.append(NetworkReceiver(colour, address))
421                         else:
422                                 return None
423                 #print str(self) + ".SelectPlayers returns " + str(players)
424                 return players
425                         
426                                 
427                         

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