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

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