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

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