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

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