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

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