diff --git a/agents/bishop/bishop.py b/agents/bishop/bishop.py old mode 100755 new mode 100644 diff --git a/agents/bishop/data b/agents/bishop/data deleted file mode 120000 index 75a080af8fdf43fc66d55d1102ce8d663b397ea0..0000000000000000000000000000000000000000 --- a/agents/bishop/data +++ /dev/null @@ -1 +0,0 @@ -../../qchess/data/ \ No newline at end of file diff --git a/agents/bishop/data/DejaVuSans.ttf b/agents/bishop/data/DejaVuSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..27cff476ef317e4666f7ffb77e82b9398a90825a Binary files /dev/null and b/agents/bishop/data/DejaVuSans.ttf differ diff --git a/agents/bishop/data/help.txt b/agents/bishop/data/help.txt new file mode 100644 index 0000000000000000000000000000000000000000..8911f3b2da55b93d35e516f01965349ee832c327 --- /dev/null +++ b/agents/bishop/data/help.txt @@ -0,0 +1,135 @@ +NAME + qchess.py - Play quantum chess + +SYNOPSIS + qchess.py [OPTIONS] [white] [black] + +DESCRIPTION + An implementation of Quantum Chess as originally described and implemented here: + http://research.cs.queensu.ca/Parallel/QuantumChess/QuantumChess.html + + Reimplemented for UCC::Progcomp 2013 + http://progcomp.ucc.asn.au + + IMPORTANT: + - This version does not implement castling or en passen rules. + - If a piece currently in a pawn state moves into the opposing back row, that state always becomes a queen. + - (The other state of the piece is unaffected). + + +ARGUMENTS + + If no arguments are given, a window should appear asking you to pick each player. + Then the game will commence using default values. + + white, black + Each of the two players in order. They need not be provided if graphics is enabled (default). + + Any arguments that do not begin with a hyphen (-) are treated as the player arguments in the order they appear. + + Player arguments that begin with '@' are treated as special players: + + @human + A human player; if graphics are enabled, this players turns are made through the GUI + + @network[:address] + A player over a network connection. + + For example, if black@host1 wants to play white@host2: + + black@host1:~$ ./qchess.py @network @human + white@host2:~$ ./qchess.py @human @network:host1 + + IMPORTANT: Only ONE of the games should give the other's address. + + @internal:name + An internal agent player + + These agents run within the qchess program (unless there is a timeout setting... never mind). + + Choices are: + + AgentRandom - Makes random moves only + + AgentBishop - Uses probability estimates and a min/max recursive (depth is only one) algorithm + - Will usually take a long time to run + +OPTIONS + + --help + Print this page + + --graphics + Enable the GUI + + If graphics are enabled (default), then the user will be prompted to choose any of the two players not supplied as arguments. + + --no-graphics + Disable the GUI + + + --reveal + If graphics are enabled, the two states for pieces will always be shown, regardless of whether both states have been revealed. + Note that this switch only affects the GUI and does not provide any information to agent players. + + If graphics are disabled, has no effect. + + --file[=filename][:events] + Replay a game saved in file, or read from stdin if no filename given + If a number of events is supplied, the game will advance that many events before stopping. + + If no players are given, the GUI will NOT ask for player selections. + The game will exit after the replay finishes. Events in the replay will be subject to the normal delay (see --delay). + + If black and white players are supplied, the game will continue using those players. + In this case, there will be no delays between events in the replay (the game starts at the end of the replay) + + (We hope that) this feature will be useful for comparing how different versions of an agent respond to the same situation. + + --log[=filename] + Log moves to a file or stdout if no filename given + + + + --delay[=time] + The game pauses between moves so that it can be followed by a human observer. + This option can be used to change the delay. If no time is given, the delay is disabled. + + If graphics are enabled (default), the delay is 0.5s by default. + If graphics are disabled, there is no delay unless this option is used. + + --timeout[=time] + Set the maximum time in seconds to wait before declaring an AI program unresponsive. + If no time is given, the timeout is disabled. + + By default the timeout is disabled. + + --blackout[=time] + Setting a blackout time will cause the display to become black if the mouse is not moved and no keys or buttons are pressed. + If no time is given, the blackout time is disabled. + + By default the blackout is disabled. + + This switch was introduced for entirely obscure purposes. + + --classical + If this option is used, the game will treat pieces "classically", ie: as in standard chess. + Note that the game does not enforce rules related to check and checkmate. + + --quantum + The game uses the quantum chess representation of pieces (default). + + +AUTHOR + Written for the UCC Programming Competition 2013 by Sam Moore. + UCC::Progcomp home page: http://progcomp.ucc.asn.au + +REPORTING BUGS + Report bugs to matches@ucc.asn.au + Join IRC channel #progcomp on irc://irc.ucc.asn.au + +COPYRIGHT + Copyright 2013 The University Computer Club, Inc. + + Contact committee@ucc.asn.au + diff --git a/agents/bishop/qchess.py b/agents/bishop/qchess.py deleted file mode 120000 index 35c6d6e8a5edf5bdf5173254d57a1c5500b7b59b..0000000000000000000000000000000000000000 --- a/agents/bishop/qchess.py +++ /dev/null @@ -1 +0,0 @@ -../../qchess/qchess.py \ No newline at end of file diff --git a/agents/bishop/qchess.py b/agents/bishop/qchess.py new file mode 100644 index 0000000000000000000000000000000000000000..8a3610bd9cfc1d3e4ba1af9565d070f9d645d6b8 --- /dev/null +++ b/agents/bishop/qchess.py @@ -0,0 +1,2837 @@ +#!/usr/bin/python -u +import random + +# I know using non-abreviated strings is inefficient, but this is python, who cares? +# Oh, yeah, this stores the number of pieces of each type in a normal chess game +piece_types = {"pawn" : 8, "bishop" : 2, "knight" : 2, "rook" : 2, "queen" : 1, "king" : 1, "unknown" : 0} + +# Class to represent a quantum chess piece +class Piece(): + def __init__(self, colour, x, y, types): + self.colour = colour # Colour (string) either "white" or "black" + self.x = x # x coordinate (0 - 8), none of this fancy 'a', 'b' shit here + self.y = y # y coordinate (0 - 8) + self.types = types # List of possible types the piece can be (should just be two) + self.current_type = "unknown" # Current type + self.choice = -1 # Index of the current type in self.types (-1 = unknown type) + + + self.last_state = None + + self.move_pattern = None + self.coverage = None + self.possible_moves = {} + + + def init_from_copy(self, c): + self.colour = c.colour + self.x = c.x + self.y = c.y + self.types = c.types[:] + self.current_type = c.current_type + self.choice = c.choice + + self.last_state = None + self.move_pattern = None + + + + # Make a string for the piece (used for debug) + def __str__(self): + return str(self.colour) + " " + str(self.current_type) + " " + str(self.types) + " at " + str(self.x) + ","+str(self.y) + + # Draw the piece in a pygame surface + def draw(self, window, grid_sz = [80,80], style="quantum"): + + # First draw the image corresponding to self.current_type + img = images[self.colour][self.current_type] + rect = img.get_rect() + if style == "classical": + offset = [-rect.width/2, -rect.height/2] + else: + offset = [-rect.width/2,-3*rect.height/4] + window.blit(img, (self.x * grid_sz[0] + grid_sz[0]/2 + offset[0], self.y * grid_sz[1] + grid_sz[1]/2 + offset[1])) + + + if style == "classical": + return + + # Draw the two possible types underneath the current_type image + for i in range(len(self.types)): + if always_reveal_states == True or self.types[i][0] != '?': + if self.types[i][0] == '?': + img = small_images[self.colour][self.types[i][1:]] + else: + img = small_images[self.colour][self.types[i]] + else: + img = small_images[self.colour]["unknown"] # If the type hasn't been revealed, show a placeholder + + + rect = img.get_rect() + offset = [-rect.width/2,-rect.height/2] + + if i == 0: + target = (self.x * grid_sz[0] + grid_sz[0]/5 + offset[0], self.y * grid_sz[1] + 3*grid_sz[1]/4 + offset[1]) + else: + target = (self.x * grid_sz[0] + 4*grid_sz[0]/5 + offset[0], self.y * grid_sz[1] + 3*grid_sz[1]/4 + offset[1]) + + window.blit(img, target) # Blit shit + + # Collapses the wave function! + def select(self): + if self.current_type == "unknown" or not self.choice in [0,1]: + self.choice = random.randint(0,1) + if self.types[self.choice][0] == '?': + self.types[self.choice] = self.types[self.choice][1:] + self.current_type = self.types[self.choice] + return self.choice + + # Uncollapses (?) the wave function! + def deselect(self): + #print "Deselect called" + if (self.x + self.y) % 2 != 0: + if (self.types[0] != self.types[1]) or (self.types[0][0] == '?' or self.types[1][0] == '?'): + self.current_type = "unknown" + self.choice = -1 + else: + self.choice = 0 # Both the two types are the same + + # The sad moment when you realise that you do not understand anything about a subject you studied for 4 years... +# --- piece.py --- # +[w,h] = [8,8] # Width and height of board(s) + +always_reveal_states = False + +# Class to represent a quantum chess board +class Board(): + # Initialise; if master=True then the secondary piece types are assigned + # Otherwise, they are left as unknown + # So you can use this class in Agent programs, and fill in the types as they are revealed + def __init__(self, style="agent"): + self.style = style + self.pieces = {"white" : [], "black" : []} + self.grid = [[None] * w for _ in range(h)] # 2D List (you can get arrays in python, somehow, but they scare me) + self.unrevealed_types = {"white" : piece_types.copy(), "black" : piece_types.copy()} + self.king = {"white" : None, "black" : None} # We need to keep track of the king, because he is important + self.max_moves = None + self.moves = 0 + self.move_stack = [] + for c in ["black", "white"]: + del self.unrevealed_types[c]["unknown"] + + if style == "empty": + return + + # Add all the pieces with known primary types + for i in range(0, 2): + + s = ["black", "white"][i] + c = self.pieces[s] + y = [0, h-1][i] + + c.append(Piece(s, 0, y, ["rook"])) + c.append(Piece(s, 1, y, ["knight"])) + c.append(Piece(s, 2, y, ["bishop"])) + k = Piece(s, 3, y, ["king", "king"]) # There can only be one ruler! + k.current_type = "king" + self.king[s] = k + c.append(k) + c.append(Piece(s, 4, y, ["queen"])) # Apparently he may have multiple wives though. + c.append(Piece(s, 5, y, ["bishop"])) + c.append(Piece(s, 6, y, ["knight"])) + c.append(Piece(s, 7, y, ["rook"])) + + if y == 0: + y += 1 + else: + y -= 1 + + # Lots of pawn + for x in range(0, w): + c.append(Piece(s, x, y, ["pawn"])) + + types_left = {} + types_left.update(piece_types) + del types_left["king"] # We don't want one of these randomly appearing (although it might make things interesting...) + del types_left["unknown"] # We certainly don't want these! + for piece in c: + # Add to grid + self.grid[piece.x][piece.y] = piece + + if len(piece.types) > 1: + continue + if style == "agent": # Assign placeholder "unknown" secondary type + piece.types.append("unknown") + continue + + elif style == "quantum": + # The master allocates the secondary types + choice = types_left.keys()[random.randint(0, len(types_left.keys())-1)] + types_left[choice] -= 1 + if types_left[choice] <= 0: + del types_left[choice] + piece.types.append('?' + choice) + elif style == "classical": + piece.types.append(piece.types[0]) + piece.current_type = piece.types[0] + piece.choice = 0 + + def clone(self): + newboard = Board(master = False) + newpieces = newboard.pieces["white"] + newboard.pieces["black"] + mypieces = self.pieces["white"] + self.pieces["black"] + + for i in range(len(mypieces)): + newpieces[i].init_from_copy(mypieces[i]) + + # Reset the board from a string + def reset_board(self, s): + self.pieces = {"white" : [], "black" : []} + self.king = {"white" : None, "black" : None} + self.grid = [[None] * w for _ in range(h)] + for x in range(w): + for y in range(h): + self.grid[x][y] = None + + for line in s.split("\n"): + if line == "": + continue + if line[0] == "#": + continue + + tokens = line.split(" ") + [x, y] = map(int, tokens[len(tokens)-1].split(",")) + current_type = tokens[1] + types = map(lambda e : e.strip(" '[],"), line.split('[')[1].split(']')[0].split(',')) + + target = Piece(tokens[0], x, y, types) + target.current_type = current_type + + try: + target.choice = types.index(current_type) + except: + target.choice = -1 + + self.pieces[tokens[0]].append(target) + if target.current_type == "king": + self.king[tokens[0]] = target + + self.grid[x][y] = target + + + def display_grid(self, window = None, grid_sz = [80,80]): + if window == None: + return # I was considering implementing a text only display, then I thought "Fuck that" + + # The indentation is getting seriously out of hand... + for x in range(0, w): + for y in range(0, h): + if (x + y) % 2 == 0: + c = pygame.Color(200,200,200) + else: + c = pygame.Color(64,64,64) + pygame.draw.rect(window, c, (x*grid_sz[0], y*grid_sz[1], (x+1)*grid_sz[0], (y+1)*grid_sz[1])) + + def display_pieces(self, window = None, grid_sz = [80,80]): + if window == None: + return + for p in self.pieces["white"] + self.pieces["black"]: + p.draw(window, grid_sz, self.style) + + # Draw the board in a pygame window + def display(self, window = None): + self.display_grid(window) + self.display_pieces(window) + + + + + def verify(self): + for x in range(w): + for y in range(h): + if self.grid[x][y] == None: + continue + if (self.grid[x][y].x != x or self.grid[x][y].y != y): + raise Exception(sys.argv[0] + ": MISMATCH " + str(self.grid[x][y]) + " should be at " + str(x) + "," + str(y)) + + # Select a piece on the board (colour is the colour of whoever is doing the selecting) + def select(self, x,y, colour=None): + if not self.on_board(x, y): # Get on board everyone! + raise Exception("BOUNDS " + str(x) + ","+str(y)) + + piece = self.grid[x][y] + if piece == None: + raise Exception("EMPTY") + + if colour != None and piece.colour != colour: + raise Exception("COLOUR " + str(piece.colour) + " not " + str(colour)) + + # I'm not quite sure why I made this return a string, but screw logical design + return str(x) + " " + str(y) + " " + str(piece.select()) + " " + str(piece.current_type) + + + # Update the board when a piece has been selected + # "type" is apparently reserved, so I'll use "state" + def update_select(self, x, y, type_index, state, sanity=True, deselect=True): + #debug(str(self) + " update_select called") + piece = self.grid[x][y] + if piece.types[type_index] == "unknown": + if not state in self.unrevealed_types[piece.colour].keys() and sanity == True: + raise Exception("SANITY: Too many " + piece.colour + " " + state + "s") + self.unrevealed_types[piece.colour][state] -= 1 + if self.unrevealed_types[piece.colour][state] <= 0: + del self.unrevealed_types[piece.colour][state] + + piece.types[type_index] = state + piece.current_type = state + + if deselect == True and len(self.possible_moves(piece)) <= 0: + piece.deselect() # Piece can't move; deselect it + + # Piece needs to recalculate moves + piece.possible_moves = None + + # Update the board when a piece has been moved + def update_move(self, x, y, x2, y2, sanity=True): + #debug(str(self) + " update_move called \""+str(x)+ " " + str(y) + " -> " + str(x2) + " " + str(y2) + "\"") + piece = self.grid[x][y] + #print "Moving " + str(x) + "," + str(y) + " to " + str(x2) + "," + str(y2) + "; possible_moves are " + str(self.possible_moves(piece)) + + if not [x2,y2] in self.possible_moves(piece) and sanity == True: + raise Exception("ILLEGAL move " + str(x2)+","+str(y2)) + + self.grid[x][y] = None + taken = self.grid[x2][y2] + if taken != None: + if taken.current_type == "king": + self.king[taken.colour] = None + self.pieces[taken.colour].remove(taken) + self.grid[x2][y2] = piece + piece.x = x2 + piece.y = y2 + + # If the piece is a pawn, and it reaches the final row, it becomes a queen + # I know you are supposed to get a choice + # But that would be effort + if piece.current_type == "pawn" and ((piece.colour == "white" and piece.y == 0) or (piece.colour == "black" and piece.y == h-1)): + if self.style == "classical": + piece.types[0] = "queen" + piece.types[1] = "queen" + else: + piece.types[piece.choice] = "queen" + piece.current_type = "queen" + + piece.deselect() # Uncollapse (?) the wavefunction! + self.moves += 1 + + # All other pieces need to recalculate moves + for p in self.pieces["white"] + self.pieces["black"]: + p.possible_moves = None + + #self.verify() + + # Update the board from a string + # Guesses what to do based on the format of the string + def update(self, result, sanity=True, deselect=True): + #debug(str(self) + " update called \""+str(result)+"\"") + # String always starts with 'x y' + try: + s = result.split(" ") + [x,y] = map(int, s[0:2]) + except: + raise Exception("GIBBERISH \""+ str(result) + "\"") # Raise expectations + + piece = self.grid[x][y] + if piece == None and sanity == True: + raise Exception("EMPTY " + str(x) + " " + str(y)) + + # If a piece is being moved, the third token is '->' + # We could get away with just using four integers, but that wouldn't look as cool + if "->" in s: + # Last two tokens are the destination + try: + [x2,y2] = map(int, s[3:]) + except: + raise Exception("GIBBERISH \"" + str(result) + "\"") # Raise the alarm + + # Move the piece (take opponent if possible) + self.update_move(x, y, x2, y2, sanity) + + else: + # Otherwise we will just assume a piece has been selected + try: + type_index = int(s[2]) # We need to know which of the two types the piece is in; that's the third token + state = s[3] # The last token is a string identifying the type + except: + raise Exception("GIBBERISH \"" + result + "\"") # Throw a hissy fit + + + # Select the piece + self.update_select(x, y, type_index, state, sanity=sanity, deselect=deselect) + + return result + + # Gets each piece that could reach the given square and the probability that it could reach that square + # Will include allied pieces that defend the attacker + def coverage(self, x, y, colour = None, reject_allied = True): + result = {} + + if colour == None: + pieces = self.pieces["white"] + self.pieces["black"] + else: + pieces = self.pieces[colour] + + for p in pieces: + prob = self.probability_grid(p, reject_allied)[x][y] + if prob > 0: + result.update({p : prob}) + + #self.verify() + return result + + + + + + # Associates each square with a probability that the piece could move into it + # Look, I'm doing all the hard work for you here... + def probability_grid(self, p, reject_allied = True): + + result = [[0.0] * w for _ in range(h)] + if not isinstance(p, Piece): + return result + + if p.current_type != "unknown": + #sys.stderr.write(sys.argv[0] + ": " + str(p) + " moves " + str(self.possible_moves(p, reject_allied)) + "\n") + for point in self.possible_moves(p, reject_allied): + result[point[0]][point[1]] = 1.0 + return result + + + for i in range(len(p.types)): + t = p.types[i] + prob = 1.0 / float(len(p.types)) + if t == "unknown" or p.types[i][0] == '?': + total_types = 0 + for t2 in self.unrevealed_types[p.colour].keys(): + total_types += self.unrevealed_types[p.colour][t2] + + for t2 in self.unrevealed_types[p.colour].keys(): + prob2 = float(self.unrevealed_types[p.colour][t2]) / float(total_types) + #p.current_type = t2 + for point in self.possible_moves(p, reject_allied, state=t2): + result[point[0]][point[1]] += prob2 * prob + + else: + #p.current_type = t + for point in self.possible_moves(p, reject_allied, state=t): + result[point[0]][point[1]] += prob + + #self.verify() + #p.current_type = "unknown" + return result + + def prob_is_type(self, p, state): + prob = 0.5 + result = 0 + for i in range(len(p.types)): + t = p.types[i] + if t == state: + result += prob + continue + if t == "unknown" or p.types[i][0] == '?': + total_prob = 0 + for t2 in self.unrevealed_types[p.colour].keys(): + total_prob += self.unrevealed_types[p.colour][t2] + for t2 in self.unrevealed_types[p.colour].keys(): + if t2 == state: + result += prob * float(self.unrevealed_types[p.colour][t2]) / float(total_prob) + + + + # Get all squares that the piece could move into + # This is probably inefficient, but I looked at some sample chess games and they seem to actually do things this way + # reject_allied indicates whether squares occupied by allied pieces will be removed + # (set to false to check for defense) + def possible_moves(self, p, reject_allied = True, state=None): + if p == None: + raise Exception("SANITY: No piece") + + + + if state != None and state != p.current_type: + old_type = p.current_type + p.current_type = state + result = self.possible_moves(p, reject_allied, state=None) + p.current_type = old_type + return result + + + + + result = [] + + + + if p.current_type == "unknown": + raise Exception("SANITY: Unknown state for piece: "+str(p)) + # The below commented out code causes things to break badly + #for t in p.types: + # if t == "unknown": + # continue + # p.current_type = t + # result += self.possible_moves(p) + #p.current_type = "unknown" + #return result + + if p.current_type == "king": + result = [[p.x-1,p.y],[p.x+1,p.y],[p.x,p.y-1],[p.x,p.y+1], [p.x-1,p.y-1],[p.x-1,p.y+1],[p.x+1,p.y-1],[p.x+1,p.y+1]] + elif p.current_type == "queen": + for d in [[-1,0],[1,0],[0,-1],[0,1],[-1,-1],[-1,1],[1,-1],[1,1]]: + result += self.scan(p.x, p.y, d[0], d[1]) + elif p.current_type == "bishop": + for d in [[-1,-1],[-1,1],[1,-1],[1,1]]: # There's a reason why bishops move diagonally + result += self.scan(p.x, p.y, d[0], d[1]) + elif p.current_type == "rook": + for d in [[-1,0],[1,0],[0,-1],[0,1]]: + result += self.scan(p.x, p.y, d[0], d[1]) + elif p.current_type == "knight": + # I would use two lines, but I'm not sure how python likes that + result = [[p.x-2, p.y-1], [p.x-2, p.y+1], [p.x+2, p.y-1], [p.x+2,p.y+1], [p.x-1,p.y-2], [p.x-1, p.y+2],[p.x+1,p.y-2],[p.x+1,p.y+2]] + elif p.current_type == "pawn": + if p.colour == "white": + + # Pawn can't move forward into occupied square + if self.on_board(p.x, p.y-1) and self.grid[p.x][p.y-1] == None: + result = [[p.x,p.y-1]] + for f in [[p.x-1,p.y-1],[p.x+1,p.y-1]]: + if not self.on_board(f[0], f[1]): + continue + if self.grid[f[0]][f[1]] != None: # Pawn can take diagonally + result.append(f) + if p.y == h-2: + # Slightly embarrassing if the pawn jumps over someone on its first move... + if self.grid[p.x][p.y-1] == None and self.grid[p.x][p.y-2] == None: + result.append([p.x, p.y-2]) + else: + # Vice versa for the black pawn + if self.on_board(p.x, p.y+1) and self.grid[p.x][p.y+1] == None: + result = [[p.x,p.y+1]] + + for f in [[p.x-1,p.y+1],[p.x+1,p.y+1]]: + if not self.on_board(f[0], f[1]): + continue + if self.grid[f[0]][f[1]] != None: + #sys.stderr.write(sys.argv[0] + " : "+str(p) + " can take " + str(self.grid[f[0]][f[1]]) + "\n") + result.append(f) + if p.y == 1: + if self.grid[p.x][p.y+1] == None and self.grid[p.x][p.y+2] == None: + result.append([p.x, p.y+2]) + + #sys.stderr.write(sys.argv[0] + " : possible_moves for " + str(p) + " " + str(result) + "\n") + + # Remove illegal moves + # Note: The result[:] creates a copy of result, so that the result.remove calls don't fuck things up + for point in result[:]: + + if (point[0] < 0 or point[0] >= w) or (point[1] < 0 or point[1] >= h): + result.remove(point) # Remove locations outside the board + continue + g = self.grid[point[0]][point[1]] + + if g != None and (g.colour == p.colour and reject_allied == True): + result.remove(point) # Remove allied pieces + + #self.verify() + + p.possible_moves = result + return result + + + # Scans in a direction until it hits a piece, returns all squares in the line + # (includes the final square (which contains a piece), but not the original square) + def scan(self, x, y, vx, vy): + p = [] + + xx = x + yy = y + while True: + xx += vx + yy += vy + if not self.on_board(xx, yy): + break + if not [xx,yy] in p: + p.append([xx, yy]) + g = self.grid[xx][yy] + if g != None: + return p + + return p + + # Returns "white", "black" or "DRAW" if the game should end + def end_condition(self): + if self.king["white"] == None: + if self.king["black"] == None: + return "DRAW" # This shouldn't happen + return "black" + elif self.king["black"] == None: + return "white" + elif len(self.pieces["white"]) == 1 and len(self.pieces["black"]) == 1: + return "DRAW" + elif self.max_moves != None and self.moves > self.max_moves: + return "DRAW" + return None + + + # I typed the full statement about 30 times before writing this function... + def on_board(self, x, y): + return (x >= 0 and x < w) and (y >= 0 and y < h) + + # Pushes a move temporarily + def push_move(self, piece, x, y): + target = self.grid[x][y] + self.move_stack.append([piece, target, piece.x, piece.y, x, y]) + [piece.x, piece.y] = [x, y] + self.grid[x][y] = piece + self.grid[piece.x][piece.y] = None + + for p in self.pieces["white"] + self.pieces["black"]: + p.possible_moves = None + + # Restore move + def pop_move(self): + #print str(self.move_stack) + [piece, target, x1, y1, x2, y2] = self.move_stack[len(self.move_stack)-1] + self.move_stack = self.move_stack[:-1] + piece.x = x1 + piece.y = y1 + self.grid[x1][y1] = piece + if target != None: + target.x = x2 + target.y = y2 + self.grid[x2][y2] = target + + for p in self.pieces["white"] + self.pieces["black"]: + p.possible_moves = None + +# --- board.py --- # +import subprocess +import select +import platform +import re + +agent_timeout = -1.0 # Timeout in seconds for AI players to make moves + # WARNING: Won't work for windows based operating systems + +if platform.system() == "Windows": + agent_timeout = -1 # Hence this + +# A player who can't play +class Player(): + def __init__(self, name, colour): + self.name = name + self.colour = colour + + def update(self, result): + return result + + def reset_board(self, s): + pass + + def __str__(self): + return self.name + "<"+str(self.colour)+">" + + def base_player(self): + return self + +# Player that runs from another process +class ExternalAgent(Player): + + + def __init__(self, name, colour): + Player.__init__(self, name, colour) + self.p = subprocess.Popen(name,bufsize=0,stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True,universal_newlines=True) + + self.send_message(colour) + + def send_message(self, s): + if agent_timeout > 0.0: + ready = select.select([], [self.p.stdin], [], agent_timeout)[1] + else: + ready = [self.p.stdin] + if self.p.stdin in ready: + #sys.stderr.write("Writing \'" + s + "\' to " + str(self.p) + "\n") + try: + self.p.stdin.write(s + "\n") + except: + raise Exception("UNRESPONSIVE") + else: + raise Exception("TIMEOUT") + + def get_response(self): + if agent_timeout > 0.0: + ready = select.select([self.p.stdout], [], [], agent_timeout)[0] + else: + ready = [self.p.stdout] + if self.p.stdout in ready: + #sys.stderr.write("Reading from " + str(self.p) + " 's stdout...\n") + try: + result = self.p.stdout.readline().strip(" \t\r\n") + #sys.stderr.write("Read \'" + result + "\' from " + str(self.p) + "\n") + return result + except: # Exception, e: + raise Exception("UNRESPONSIVE") + else: + raise Exception("TIMEOUT") + + def select(self): + + self.send_message("SELECTION?") + line = self.get_response() + + try: + m = re.match("\s*(\d+)\s+(\d+)\s*", line) + result = map(int, [m.group(1), m.group(2)]) + except: + raise Exception("GIBBERISH \"" + str(line) + "\"") + return result + + def update(self, result): + #print "Update " + str(result) + " called for AgentPlayer" + self.send_message(result) + return result + + def get_move(self): + + self.send_message("MOVE?") + line = self.get_response() + + try: + m = re.match("\s*(\d+)\s+(\d+)\s*", line) + result = map(int, [m.group(1), m.group(2)]) + + except: + raise Exception("GIBBERISH \"" + str(line) + "\"") + return result + + def reset_board(self, s): + self.send_message("BOARD") + for line in s.split("\n"): + self.send_message(line.strip(" \r\n")) + self.send_message("END BOARD") + + def quit(self, final_result): + try: + self.send_message("QUIT " + final_result) + except: + self.p.kill() + +# So you want to be a player here? +class HumanPlayer(Player): + def __init__(self, name, colour): + Player.__init__(self, name, colour) + + # Select your preferred account + def select(self): + if isinstance(graphics, GraphicsThread): + # Basically, we let the graphics thread do some shit and then return that information to the game thread + graphics.cond.acquire() + # We wait for the graphics thread to select a piece + while graphics.stopped() == False and graphics.state["select"] == None: + graphics.cond.wait() # The difference between humans and machines is that humans sleep + select = graphics.state["select"] + + + graphics.cond.release() + if graphics.stopped(): + return [-1,-1] + return [select.x, select.y] + else: + # Since I don't display the board in this case, I'm not sure why I filled it in... + while True: + sys.stdout.write("SELECTION?\n") + try: + p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + except: + sys.stderr.write("ILLEGAL GIBBERISH\n") + continue + # It's your move captain + def get_move(self): + if isinstance(graphics, GraphicsThread): + graphics.cond.acquire() + while graphics.stopped() == False and graphics.state["dest"] == None: + graphics.cond.wait() + graphics.cond.release() + + return graphics.state["dest"] + else: + + while True: + sys.stdout.write("MOVE?\n") + try: + p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + except: + sys.stderr.write("ILLEGAL GIBBERISH\n") + continue + + # Are you sure you want to quit? + def quit(self, final_result): + if graphics == None: + sys.stdout.write("QUIT " + final_result + "\n") + + # Completely useless function + def update(self, result): + if isinstance(graphics, GraphicsThread): + pass + else: + sys.stdout.write(result + "\n") + return result + + +# Default internal player (makes random moves) +class InternalAgent(Player): + def __init__(self, name, colour): + Player.__init__(self, name, colour) + self.choice = None + + self.board = Board(style = "agent") + + + + def update(self, result): + + self.board.update(result) + #self.board.verify() + return result + + def reset_board(self, s): + self.board.reset_board(s) + + def quit(self, final_result): + pass + +class AgentRandom(InternalAgent): + def __init__(self, name, colour): + InternalAgent.__init__(self, name, colour) + + def select(self): + while True: + self.choice = self.board.pieces[self.colour][random.randint(0, len(self.board.pieces[self.colour])-1)] + all_moves = [] + # Check that the piece has some possibility to move + tmp = self.choice.current_type + if tmp == "unknown": # For unknown pieces, try both types + for t in self.choice.types: + if t == "unknown": + continue + self.choice.current_type = t + all_moves += self.board.possible_moves(self.choice) + else: + all_moves = self.board.possible_moves(self.choice) + self.choice.current_type = tmp + if len(all_moves) > 0: + break + return [self.choice.x, self.choice.y] + + def get_move(self): + moves = self.board.possible_moves(self.choice) + move = moves[random.randint(0, len(moves)-1)] + return move + + +# Terrible, terrible hacks + +def run_agent(agent): + #sys.stderr.write(sys.argv[0] + " : Running agent " + str(agent) + "\n") + while True: + line = sys.stdin.readline().strip(" \r\n") + if line == "SELECTION?": + #sys.stderr.write(sys.argv[0] + " : Make selection\n") + [x,y] = agent.select() # Gets your agent's selection + #sys.stderr.write(sys.argv[0] + " : Selection was " + str(agent.choice) + "\n") + sys.stdout.write(str(x) + " " + str(y) + "\n") + elif line == "MOVE?": + #sys.stderr.write(sys.argv[0] + " : Make move\n") + [x,y] = agent.get_move() # Gets your agent's move + sys.stdout.write(str(x) + " " + str(y) + "\n") + elif line.split(" ")[0] == "QUIT": + #sys.stderr.write(sys.argv[0] + " : Quitting\n") + agent.quit(" ".join(line.split(" ")[1:])) # Quits the game + break + elif line.split(" ")[0] == "BOARD": + s = "" + line = sys.stdin.readline().strip(" \r\n") + while line != "END BOARD": + s += line + "\n" + line = sys.stdin.readline().strip(" \r\n") + agent.board.reset_board(s) + + else: + agent.update(line) # Updates agent.board + return 0 + + +# Sort of works? + +class ExternalWrapper(ExternalAgent): + def __init__(self, agent): + run = "python -u -c \"import sys;import os;from qchess import *;agent = " + agent.__class__.__name__ + "('" + agent.name + "','"+agent.colour+"');sys.stdin.readline();sys.exit(run_agent(agent))\"" + # str(run) + ExternalAgent.__init__(self, run, agent.colour) + + + +# --- player.py --- # +# A sample agent + + +class AgentBishop(AgentRandom): # Inherits from AgentRandom (in qchess) + def __init__(self, name, colour): + InternalAgent.__init__(self, name, colour) + self.value = {"pawn" : 1, "bishop" : 3, "knight" : 3, "rook" : 5, "queen" : 9, "king" : 100, "unknown" : 4} + + self.aggression = 2.0 # Multiplier for scoring due to aggressive actions + self.defence = 1.0 # Multiplier for scoring due to defensive actions + + self.depth = 0 # Current depth + self.max_depth = 2 # Recurse this many times (for some reason, makes more mistakes when this is increased???) + self.recurse_for = -1 # Recurse for the best few moves each times (less than 0 = all moves) + + for p in self.board.pieces["white"] + self.board.pieces["black"]: + p.last_moves = None + p.selected_moves = None + + + + def get_value(self, piece): + if piece == None: + return 0.0 + return float(self.value[piece.types[0]] + self.value[piece.types[1]]) / 2.0 + + # Score possible moves for the piece + + def prioritise_moves(self, piece): + + #sys.stderr.write(sys.argv[0] + " : " + str(self) + " prioritise called for " + str(piece) + "\n") + + + + grid = self.board.probability_grid(piece) + #sys.stderr.write("\t Probability grid " + str(grid) + "\n") + moves = [] + for x in range(w): + for y in range(h): + if grid[x][y] < 0.3: # Throw out moves with < 30% probability + #sys.stderr.write("\tReject " + str(x) + "," + str(y) + " (" + str(grid[x][y]) + ")\n") + continue + + target = self.board.grid[x][y] + + + + + # Get total probability that the move is protected + self.board.push_move(piece, x, y) + + + + defenders = self.board.coverage(x, y, piece.colour, reject_allied = False) + d_prob = 0.0 + for d in defenders.keys(): + d_prob += defenders[d] + if len(defenders.keys()) > 0: + d_prob /= float(len(defenders.keys())) + + if (d_prob > 1.0): + d_prob = 1.0 + + # Get total probability that the move is threatened + attackers = self.board.coverage(x, y, opponent(piece.colour), reject_allied = False) + a_prob = 0.0 + for a in attackers.keys(): + a_prob += attackers[a] + if len(attackers.keys()) > 0: + a_prob /= float(len(attackers.keys())) + + if (a_prob > 1.0): + a_prob = 1.0 + + self.board.pop_move() + + + + # Score of the move + value = self.aggression * (1.0 + d_prob) * self.get_value(target) - self.defence * (1.0 - d_prob) * a_prob * self.get_value(piece) + + # Adjust score based on movement of piece out of danger + attackers = self.board.coverage(piece.x, piece.y, opponent(piece.colour)) + s_prob = 0.0 + for a in attackers.keys(): + s_prob += attackers[a] + if len(attackers.keys()) > 0: + s_prob /= float(len(attackers.keys())) + + if (s_prob > 1.0): + s_prob = 1.0 + value += self.defence * s_prob * self.get_value(piece) + + # Adjust score based on probability that the move is actually possible + moves.append([[x, y], grid[x][y] * value]) + + moves.sort(key = lambda e : e[1], reverse = True) + #sys.stderr.write(sys.argv[0] + ": Moves for " + str(piece) + " are " + str(moves) + "\n") + + piece.last_moves = moves + piece.selected_moves = None + + + + + return moves + + def select_best(self, colour): + + self.depth += 1 + all_moves = {} + for p in self.board.pieces[colour]: + self.choice = p # Temporarily pick that piece + m = self.prioritise_moves(p) + if len(m) > 0: + all_moves.update({p : m[0]}) + + if len(all_moves.items()) <= 0: + return None + + + opts = all_moves.items() + opts.sort(key = lambda e : e[1][1], reverse = True) + + if self.depth >= self.max_depth: + self.depth -= 1 + return list(opts[0]) + + if self.recurse_for >= 0: + opts = opts[0:self.recurse_for] + #sys.stderr.write(sys.argv[0] + " : Before recurse, options are " + str(opts) + "\n") + + # Take the best few moves, and recurse + for choice in opts[0:self.recurse_for]: + [xx,yy] = [choice[0].x, choice[0].y] # Remember position + [nx,ny] = choice[1][0] # Target + [choice[0].x, choice[0].y] = [nx, ny] # Set position + target = self.board.grid[nx][ny] # Remember piece in spot + self.board.grid[xx][yy] = None # Remove piece + self.board.grid[nx][ny] = choice[0] # Replace with moving piece + + # Recurse + best_enemy_move = self.select_best(opponent(choice[0].colour)) + choice[1][1] -= best_enemy_move[1][1] / float(self.depth + 1.0) + + [choice[0].x, choice[0].y] = [xx, yy] # Restore position + self.board.grid[nx][ny] = target # Restore taken piece + self.board.grid[xx][yy] = choice[0] # Restore moved piece + + + + opts.sort(key = lambda e : e[1][1], reverse = True) + #sys.stderr.write(sys.argv[0] + " : After recurse, options are " + str(opts) + "\n") + + self.depth -= 1 + return list(opts[0]) + + + + # Returns [x,y] of selected piece + def select(self): + #sys.stderr.write("Getting choice...") + self.choice = self.select_best(self.colour)[0] + + #sys.stderr.write(" Done " + str(self.choice)+"\n") + return [self.choice.x, self.choice.y] + + # Returns [x,y] of square to move selected piece into + def get_move(self): + #sys.stderr.write("Choice is " + str(self.choice) + "\n") + self.choice.selected_moves = self.choice.last_moves + moves = self.prioritise_moves(self.choice) + if len(moves) > 0: + return moves[0][0] + else: + return AgentRandom.get_move(self) + +# --- agent_bishop.py --- # +import multiprocessing + +# Hacky alternative to using select for timing out players + +# WARNING: Do not wrap around HumanPlayer or things breakify +# WARNING: Do not use in general or things breakify + +class Sleeper(multiprocessing.Process): + def __init__(self, timeout): + multiprocessing.Process.__init__(self) + self.timeout = timeout + + def run(self): + time.sleep(self.timeout) + + +class Worker(multiprocessing.Process): + def __init__(self, function, args, q): + multiprocessing.Process.__init__(self) + self.function = function + self.args = args + self.q = q + + def run(self): + #print str(self) + " runs " + str(self.function) + " with args " + str(self.args) + self.q.put(self.function(*self.args)) + + + +def TimeoutFunction(function, args, timeout): + q = multiprocessing.Queue() + w = Worker(function, args, q) + s = Sleeper(timeout) + w.start() + s.start() + while True: # Busy loop of crappyness + if not w.is_alive(): + s.terminate() + result = q.get() + w.join() + #print "TimeoutFunction gets " + str(result) + return result + elif not s.is_alive(): + w.terminate() + s.join() + raise Exception("TIMEOUT") + + + + +# A player that wraps another player and times out its moves +# Uses threads +# A (crappy) alternative to the use of select() +class TimeoutPlayer(Player): + def __init__(self, base_player, timeout): + Player.__init__(self, base_player.name, base_player.colour) + self.base_player = base_player + self.timeout = timeout + + def select(self): + return TimeoutFunction(self.base_player.select, [], self.timeout) + + + def get_move(self): + return TimeoutFunction(self.base_player.get_move, [], self.timeout) + + def update(self, result): + return TimeoutFunction(self.base_player.update, [result], self.timeout) + + def quit(self, final_result): + return TimeoutFunction(self.base_player.quit, [final_result], self.timeout) +# --- timeout_player.py --- # +import socket +import select + +network_timeout_start = -1.0 # Timeout in seconds to wait for the start of a message +network_timeout_delay = 1.0 # Maximum time between two characters being received + +class NetworkPlayer(Player): + def __init__(self, colour, network, player): + Player.__init__(self, "@network:"+str(network.address), colour) + self.player = player + self.network = network + + def __str__(self): + return "NetworkPlayer<"+str(self.colour)+","+str(self.player)+">" + + def select(self): + #debug(str(self) + " select called") + if self.player != None: + s = self.player.select() + self.send_message(str(s[0]) + " " + str(s[1])) + else: + s = map(int, self.get_response().split(" ")) + for p in game.players: + if p != self and isinstance(p, NetworkPlayer) and p.player == None: + p.network.send_message(str(s[0]) + " " + str(s[1])) + if s == [-1,-1]: + game.final_result = "network terminate" + game.stop() + return s + + def send_message(self, message): + #debug(str(self) + " send_message(\""+str(message)+"\") called") + self.network.send_message(message) + + def get_response(self): + #debug(str(self) + " get_response() called") + s = self.network.get_response() + #debug(str(self) + " get_response() returns \""+str(s)+"\"") + return s + + + def get_move(self): + #debug(str(self) + " get_move called") + if self.player != None: + s = self.player.get_move() + self.send_message(str(s[0]) + " " + str(s[1])) + else: + s = map(int, self.get_response().split(" ")) + for p in game.players: + if p != self and isinstance(p, NetworkPlayer) and p.player == None: + p.network.send_message(str(s[0]) + " " + str(s[1])) + + if s == [-1,-1]: + game.final_result = "network terminate" + game.stop() + return s + + def update(self, result): + #debug(str(self) + " update(\""+str(result)+"\") called") + if self.network.server == True: + if self.player == None: + self.send_message(result) + elif self.player != None: + result = self.get_response() + if result == "-1 -1": + game.final_result = "network terminate" + game.stop() + return "-1 -1" + self.board.update(result, deselect=False) + + + + if self.player != None: + result = self.player.update(result) + + return result + + + + def base_player(self): + if self.player == None: + return self + else: + return self.player.base_player() + + def quit(self, result): + try: + self.send_message("-1 -1") + except: + pass + +class Network(): + def __init__(self, address = (None,4562)): + self.socket = socket.socket() + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + #self.socket.setblocking(0) + self.address = address + self.server = (address[0] == None) + + + self.connected = False + + def connect(self): + #debug(str(self) + "Tries to connect") + self.connected = True + if self.address[0] == None: + self.host = "0.0.0.0" #socket.gethostname() # Breaks things??? + self.socket.bind((self.host, self.address[1])) + self.socket.listen(5) + + self.src, self.actual_address = self.socket.accept() + + self.src.send("ok\n") + s = self.get_response() + if s == "QUIT": + self.src.close() + return + elif s != "ok": + self.src.close() + self.__init__(colour, (self.address[0], int(s)), baseplayer) + return + + else: + time.sleep(0.3) + self.socket.connect(self.address) + self.src = self.socket + self.src.send("ok\n") + s = self.get_response() + if s == "QUIT": + self.src.close() + return + elif s != "ok": + self.src.close() + self.__init__(colour, (self.address[0], int(s)), baseplayer) + return + + + + def __str__(self): + return "@network:"+str(self.address) + + def get_response(self): + + # Timeout the start of the message (first character) + if network_timeout_start > 0.0: + ready = select.select([self.src], [], [], network_timeout_start)[0] + else: + ready = [self.src] + if self.src in ready: + s = self.src.recv(1) + else: + raise Exception("UNRESPONSIVE") + + + while s[len(s)-1] != '\n': + # Timeout on each character in the message + if network_timeout_delay > 0.0: + ready = select.select([self.src], [], [], network_timeout_delay)[0] + else: + ready = [self.src] + if self.src in ready: + s += self.src.recv(1) + else: + raise Exception("UNRESPONSIVE") + + + return s.strip(" \r\n") + + def send_message(self,s): + if network_timeout_start > 0.0: + ready = select.select([], [self.src], [], network_timeout_start)[1] + else: + ready = [self.src] + + if self.src in ready: + self.src.send(s + "\n") + else: + raise Exception("UNRESPONSIVE") + + + + def close(self): + self.src.shutdown() + self.src.close() +# --- network.py --- # +import threading + +# A thread that can be stopped! +# Except it can only be stopped if it checks self.stopped() periodically +# So it can sort of be stopped +class StoppableThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self._stop = threading.Event() + + def stop(self): + self._stop.set() + + def stopped(self): + return self._stop.isSet() +# --- thread_util.py --- # +log_files = [] +import datetime +import urllib2 + +class LogFile(): + def __init__(self, log, name): + self.name = name + self.log = log + self.logged = [] + self.log.write("# Log starts " + str(datetime.datetime.now()) + "\n") + + def write(self, s): + now = datetime.datetime.now() + self.log.write(str(now) + " : " + s + "\n") + self.logged.append((now, s)) + + def setup(self, board, players): + + for p in players: + self.log.write("# " + str(p.colour) + " : " + str(p.name) + "\n") + + self.log.write("# Initial board\n") + for x in range(0, w): + for y in range(0, h): + if board.grid[x][y] != None: + self.log.write(str(board.grid[x][y]) + "\n") + + self.log.write("# Start game\n") + + def close(self): + self.log.write("# EOF\n") + if self.log != sys.stdout: + self.log.close() + +class ShortLog(LogFile): + def __init__(self, file_name): + if file_name == "": + self.log = sys.stdout + else: + self.log = open(file_name, "w", 0) + LogFile.__init__(self, self.log, "@"+file_name) + self.file_name = file_name + self.phase = 0 + + def write(self, s): + now = datetime.datetime.now() + self.logged.append((now, s)) + + if self.phase == 0: + if self.log != sys.stdout: + self.log.close() + self.log = open(self.file_name, "w", 0) + self.log.write("# Short log updated " + str(datetime.datetime.now()) + "\n") + LogFile.setup(self, game.board, game.players) + + elif self.phase == 1: + for message in self.logged[len(self.logged)-2:]: + self.log.write(str(message[0]) + " : " + message[1] + "\n") + + self.phase = (self.phase + 1) % 2 + + def close(self): + if self.phase == 1: + ending = self.logged[len(self.logged)-1] + self.log.write(str(ending[0]) + " : " + ending[1] + "\n") + self.log.write("# EOF\n") + if self.log != sys.stdout: + self.log.close() + + +class HeadRequest(urllib2.Request): + def get_method(self): + return "HEAD" + +class HttpGetter(StoppableThread): + def __init__(self, address): + StoppableThread.__init__(self) + self.address = address + self.log = urllib2.urlopen(address) + self.lines = [] + self.lock = threading.RLock() #lock for access of self.state + self.cond = threading.Condition() # conditional + + def run(self): + while not self.stopped(): + line = self.log.readline() + if line == "": + date_mod = datetime.datetime.strptime(self.log.headers['last-modified'], "%a, %d %b %Y %H:%M:%S GMT") + self.log.close() + + next_log = urllib2.urlopen(HeadRequest(self.address)) + date_new = datetime.datetime.strptime(next_log.headers['last-modified'], "%a, %d %b %Y %H:%M:%S GMT") + while date_new <= date_mod and not self.stopped(): + next_log = urllib2.urlopen(HeadRequest(self.address)) + date_new = datetime.datetime.strptime(next_log.headers['last-modified'], "%a, %d %b %Y %H:%M:%S GMT") + if self.stopped(): + break + + self.log = urllib2.urlopen(self.address) + line = self.log.readline() + + self.cond.acquire() + self.lines.append(line) + self.cond.notifyAll() + self.cond.release() + + #sys.stderr.write(" HttpGetter got \'" + str(line) + "\'\n") + + self.log.close() + + + + + +class HttpReplay(): + def __init__(self, address): + self.getter = HttpGetter(address) + self.getter.start() + + def readline(self): + self.getter.cond.acquire() + while len(self.getter.lines) == 0: + self.getter.cond.wait() + + result = self.getter.lines[0] + self.getter.lines = self.getter.lines[1:] + self.getter.cond.release() + + return result + + + def close(self): + self.getter.stop() + +class FileReplay(): + def __init__(self, filename): + self.f = open(filename, "r", 0) + self.filename = filename + self.mod = os.path.getmtime(filename) + self.count = 0 + + def readline(self): + line = self.f.readline() + + while line == "": + mod2 = os.path.getmtime(self.filename) + if mod2 > self.mod: + #sys.stderr.write("File changed!\n") + self.mod = mod2 + self.f.close() + self.f = open(self.filename, "r", 0) + + new_line = self.f.readline() + + if " ".join(new_line.split(" ")[0:3]) != "# Short log": + for i in range(self.count): + new_line = self.f.readline() + #sys.stderr.write("Read back " + str(i) + ": " + str(new_line) + "\n") + new_line = self.f.readline() + else: + self.count = 0 + + line = new_line + + self.count += 1 + return line + + def close(self): + self.f.close() + + +def log(s): + for l in log_files: + l.write(s) + +def debug(s): + sys.stderr.write("# DEBUG: " + s + "\n") + + +def log_init(board, players): + for l in log_files: + l.setup(board, players) + +# --- log.py --- # + + + + + +# A thread that runs the game +class GameThread(StoppableThread): + def __init__(self, board, players, server = True): + StoppableThread.__init__(self) + self.board = board + self.players = players + self.state = {"turn" : None} # The game state + self.error = 0 # Whether the thread exits with an error + self.lock = threading.RLock() #lock for access of self.state + self.cond = threading.Condition() # conditional for some reason, I forgot + self.final_result = "" + self.server = server + + + + + + + # Run the game (run in new thread with start(), run in current thread with run()) + def run(self): + result = "" + while not self.stopped(): + + for p in self.players: + with self.lock: + self.state["turn"] = p.base_player() + #try: + if True: + [x,y] = p.select() # Player selects a square + if self.stopped(): + #debug("Quitting in select") + break + + if isinstance(p, NetworkPlayer): + if p.network.server == True: + result = self.board.select(x, y, colour = p.colour) + else: + result = None + + else: + result = self.board.select(x, y, colour = p.colour) + + result = p.update(result) + if self.stopped(): + break + for p2 in self.players: + if p2 == p: + continue + p2.update(result) # Inform players of what happened + if self.stopped(): + break + + if self.stopped(): + break + + + log(result) + + target = self.board.grid[x][y] + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = self.board.possible_moves(target) + graphics.state["select"] = target + + time.sleep(turn_delay) + + + if len(self.board.possible_moves(target)) == 0: + #print "Piece cannot move" + target.deselect() + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = None + graphics.state["select"] = None + graphics.state["dest"] = None + continue + + try: + [x2,y2] = p.get_move() # Player selects a destination + except: + self.stop() + + if self.stopped(): + #debug("Quitting in get_move") + break + + if isinstance(p, NetworkPlayer): + if p.network.server == True: + result = str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2) + self.board.update_move(x, y, x2, y2) + else: + result = None + + else: + result = str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2) + self.board.update_move(x, y, x2, y2) + + result = p.update(result) + if self.stopped(): + break + for p2 in self.players: + if p2 == p: + continue + p2.update(result) # Inform players of what happened + if self.stopped(): + break + + if self.stopped(): + break + + + + log(result) + + + + + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = [[x2,y2]] + + time.sleep(turn_delay) + + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["select"] = None + graphics.state["dest"] = None + graphics.state["moves"] = None + + # Commented out exception stuff for now, because it makes it impossible to tell if I made an IndentationError somewhere + # except Exception,e: + # result = e.message + # #sys.stderr.write(result + "\n") + # + # self.stop() + # with self.lock: + # self.final_result = self.state["turn"].colour + " " + e.message + + end = self.board.end_condition() + if end != None: + with self.lock: + if end == "DRAW": + self.final_result = self.state["turn"].colour + " " + end + else: + self.final_result = end + self.stop() + + if self.stopped(): + break + + + for p2 in self.players: + p2.quit(self.final_result) + + log(self.final_result) + + if isinstance(graphics, GraphicsThread): + graphics.stop() + + +# A thread that replays a log file +class ReplayThread(GameThread): + def __init__(self, players, src, end=False,max_moves=None): + self.board = Board(style="empty") + self.board.max_moves = max_moves + GameThread.__init__(self, self.board, players) + self.src = src + self.end = end + + self.reset_board(self.src.readline()) + + def reset_board(self, line): + agent_str = "" + self_str = "" + while line != "# Start game" and line != "# EOF": + + while line == "": + line = self.src.readline().strip(" \r\n") + continue + + if line[0] == '#': + line = self.src.readline().strip(" \r\n") + continue + + self_str += line + "\n" + + if self.players[0].name == "dummy" and self.players[1].name == "dummy": + line = self.src.readline().strip(" \r\n") + continue + + tokens = line.split(" ") + types = map(lambda e : e.strip("[] ,'"), tokens[2:4]) + for i in range(len(types)): + if types[i][0] == "?": + types[i] = "unknown" + + agent_str += tokens[0] + " " + tokens[1] + " " + str(types) + " ".join(tokens[4:]) + "\n" + line = self.src.readline().strip(" \r\n") + + for p in self.players: + p.reset_board(agent_str) + + + self.board.reset_board(self_str) + + + def run(self): + move_count = 0 + last_line = "" + line = self.src.readline().strip(" \r\n") + while line != "# EOF": + + + if self.stopped(): + break + + if len(line) <= 0: + continue + + + if line[0] == '#': + last_line = line + line = self.src.readline().strip(" \r\n") + continue + + tokens = line.split(" ") + if tokens[0] == "white" or tokens[0] == "black": + self.reset_board(line) + last_line = line + line = self.src.readline().strip(" \r\n") + continue + + move = line.split(":") + move = move[len(move)-1].strip(" \r\n") + tokens = move.split(" ") + + + try: + [x,y] = map(int, tokens[0:2]) + except: + last_line = line + self.stop() + break + + log(move) + + target = self.board.grid[x][y] + with self.lock: + if target.colour == "white": + self.state["turn"] = self.players[0] + else: + self.state["turn"] = self.players[1] + + move_piece = (tokens[2] == "->") + if move_piece: + [x2,y2] = map(int, tokens[len(tokens)-2:]) + + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["select"] = target + + if not move_piece: + self.board.update_select(x, y, int(tokens[2]), tokens[len(tokens)-1]) + if isinstance(graphics, GraphicsThread): + with graphics.lock: + if target.current_type != "unknown": + graphics.state["moves"] = self.board.possible_moves(target) + else: + graphics.state["moves"] = None + time.sleep(turn_delay) + else: + self.board.update_move(x, y, x2, y2) + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = [[x2,y2]] + time.sleep(turn_delay) + with graphics.lock: + graphics.state["select"] = None + graphics.state["moves"] = None + graphics.state["dest"] = None + + + + + + for p in self.players: + p.update(move) + + last_line = line + line = self.src.readline().strip(" \r\n") + + + end = self.board.end_condition() + if end != None: + self.final_result = end + self.stop() + break + + + + + + + + + + + + + + + + if self.end and isinstance(graphics, GraphicsThread): + #graphics.stop() + pass # Let the user stop the display + elif not self.end and self.board.end_condition() == None: + global game + # Work out the last move + + t = last_line.split(" ") + if t[len(t)-2] == "black": + self.players.reverse() + elif t[len(t)-2] == "white": + pass + elif self.state["turn"] != None and self.state["turn"].colour == "white": + self.players.reverse() + + + game = GameThread(self.board, self.players) + game.run() + else: + pass + + + +def opponent(colour): + if colour == "white": + return "black" + else: + return "white" +# --- game.py --- # +try: + import pygame +except: + pass +import os + +# Dictionary that stores the unicode character representations of the different pieces +# Chess was clearly the reason why unicode was invented +# For some reason none of the pygame chess implementations I found used them! +piece_char = {"white" : {"king" : u'\u2654', + "queen" : u'\u2655', + "rook" : u'\u2656', + "bishop" : u'\u2657', + "knight" : u'\u2658', + "pawn" : u'\u2659', + "unknown" : '?'}, + "black" : {"king" : u'\u265A', + "queen" : u'\u265B', + "rook" : u'\u265C', + "bishop" : u'\u265D', + "knight" : u'\u265E', + "pawn" : u'\u265F', + "unknown" : '?'}} + +images = {"white" : {}, "black" : {}} +small_images = {"white" : {}, "black" : {}} + +def create_images(grid_sz, font_name=os.path.join(os.path.curdir, "data", "DejaVuSans.ttf")): + + # Get the font sizes + l_size = 5*(grid_sz[0] / 8) + s_size = 3*(grid_sz[0] / 8) + + for c in piece_char.keys(): + + if c == "black": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0))}) + small_images[c].update({p : pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0))}) + elif c == "white": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size+1).render(piece_char["black"][p], True,(255,255,255))}) + images[c][p].blit(pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0)),(0,0)) + small_images[c].update({p : pygame.font.Font(font_name, s_size+1).render(piece_char["black"][p],True,(255,255,255))}) + small_images[c][p].blit(pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0)),(0,0)) + + +def load_images(image_dir=os.path.join(os.path.curdir, "data", "images")): + if not os.path.exists(image_dir): + raise Exception("Couldn't load images from " + image_dir + " (path doesn't exist)") + for c in piece_char.keys(): + for p in piece_char[c].keys(): + images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + ".png"))}) + small_images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + "_small.png"))}) +# --- images.py --- # +graphics_enabled = True + +try: + import pygame + os.environ["SDL_VIDEO_ALLOW_SCREENSAVER"] = "1" +except: + graphics_enabled = False + +import time + + + +# A thread to make things pretty +class GraphicsThread(StoppableThread): + def __init__(self, board, title = "UCC::Progcomp 2013 - QChess", grid_sz = [80,80]): + StoppableThread.__init__(self) + + self.board = board + pygame.init() + self.window = pygame.display.set_mode((grid_sz[0] * w, grid_sz[1] * h)) + pygame.display.set_caption(title) + + #print "Initialised properly" + + self.grid_sz = grid_sz[:] + self.state = {"select" : None, "dest" : None, "moves" : None, "overlay" : None, "coverage" : None} + self.error = 0 + self.lock = threading.RLock() + self.cond = threading.Condition() + self.sleep_timeout = None + self.last_event = time.time() + self.blackout = False + + #print "Test font" + pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 32).render("Hello", True,(0,0,0)) + + #load_images() + create_images(grid_sz) + + """ + for c in images.keys(): + for p in images[c].keys(): + images[c][p] = images[c][p].convert(self.window) + small_images[c][p] = small_images[c][p].convert(self.window) + """ + + + + + + # On the run from the world + def run(self): + + while not self.stopped(): + + if self.sleep_timeout == None or (time.time() - self.last_event) < self.sleep_timeout: + + #print "Display grid" + self.board.display_grid(window = self.window, grid_sz = self.grid_sz) # Draw the board + + #print "Display overlay" + self.overlay() + + #print "Display pieces" + self.board.display_pieces(window = self.window, grid_sz = self.grid_sz) # Draw the board + self.blackout = False + + elif pygame.mouse.get_focused() and not self.blackout: + os.system("xset dpms force off") + self.blackout = True + self.window.fill((0,0,0)) + + pygame.display.flip() + + for event in pygame.event.get(): + self.last_event = time.time() + if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_q): + if isinstance(game, GameThread): + with game.lock: + game.final_result = "" + if game.state["turn"] != None: + game.final_result = game.state["turn"].colour + " " + game.final_result += "terminated" + game.stop() + self.stop() + break + elif event.type == pygame.MOUSEBUTTONDOWN: + self.mouse_down(event) + + elif event.type == pygame.MOUSEBUTTONUP: + self.mouse_up(event) + + + + + + + + + self.message("Game ends, result \""+str(game.final_result) + "\"") + time.sleep(1) + + # Wake up anyone who is sleeping + self.cond.acquire() + self.cond.notify() + self.cond.release() + + pygame.quit() # Time to say goodbye + + # Mouse release event handler + def mouse_up(self, event): + if event.button == 3: + with self.lock: + self.state["overlay"] = None + elif event.button == 2: + with self.lock: + self.state["coverage"] = None + + # Mouse click event handler + def mouse_down(self, event): + if event.button == 1: + m = [event.pos[i] / self.grid_sz[i] for i in range(2)] + if isinstance(game, GameThread): + with game.lock: + p = game.state["turn"] + else: + p = None + + + if isinstance(p, HumanPlayer): + with self.lock: + s = self.board.grid[m[0]][m[1]] + select = self.state["select"] + if select == None: + if s != None and s.colour != p.colour: + self.message("Wrong colour") # Look at all this user friendliness! + time.sleep(1) + return + # Notify human player of move + self.cond.acquire() + with self.lock: + self.state["select"] = s + self.state["dest"] = None + self.cond.notify() + self.cond.release() + return + + if select == None: + return + + + if self.state["moves"] == None: + return + + if not m in self.state["moves"]: + self.message("Illegal Move") # I still think last year's mouse interface was adequate + time.sleep(2) + return + + with self.lock: + if self.state["dest"] == None: + self.cond.acquire() + self.state["dest"] = m + self.state["select"] = None + self.state["moves"] = None + self.cond.notify() + self.cond.release() + elif event.button == 3: + m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))] + if isinstance(game, GameThread): + with game.lock: + p = game.state["turn"] + else: + p = None + + + if isinstance(p, HumanPlayer): + with self.lock: + self.state["overlay"] = self.board.probability_grid(self.board.grid[m[0]][m[1]]) + + elif event.button == 2: + m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))] + if isinstance(game, GameThread): + with game.lock: + p = game.state["turn"] + else: + p = None + + + if isinstance(p, HumanPlayer): + with self.lock: + self.state["coverage"] = self.board.coverage(m[0], m[1], None, self.state["select"]) + + # Draw the overlay + def overlay(self): + + square_img = pygame.Surface((self.grid_sz[0], self.grid_sz[1]),pygame.SRCALPHA) # A square image + # Draw square over the selected piece + with self.lock: + select = self.state["select"] + if select != None: + mp = [self.grid_sz[i] * [select.x, select.y][i] for i in range(len(self.grid_sz))] + square_img.fill(pygame.Color(0,255,0,64)) + self.window.blit(square_img, mp) + # If a piece is selected, draw all reachable squares + # (This quality user interface has been patented) + with self.lock: + m = self.state["moves"] + if m != None: + square_img.fill(pygame.Color(255,0,0,128)) # Draw them in blood red + for move in m: + mp = [self.grid_sz[i] * move[i] for i in range(2)] + self.window.blit(square_img, mp) + # If a piece is overlayed, show all squares that it has a probability to reach + with self.lock: + m = self.state["overlay"] + if m != None: + for x in range(w): + for y in range(h): + if m[x][y] > 0.0: + mp = [self.grid_sz[i] * [x,y][i] for i in range(2)] + square_img.fill(pygame.Color(255,0,255,int(m[x][y] * 128))) # Draw in purple + self.window.blit(square_img, mp) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14) + text = font.render("{0:.2f}".format(round(m[x][y],2)), 1, pygame.Color(0,0,0)) + self.window.blit(text, mp) + + # If a square is selected, highlight all pieces that have a probability to reach it + with self.lock: + m = self.state["coverage"] + if m != None: + for p in m: + mp = [self.grid_sz[i] * [p.x,p.y][i] for i in range(2)] + square_img.fill(pygame.Color(0,255,255, int(m[p] * 196))) # Draw in pale blue + self.window.blit(square_img, mp) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14) + text = font.render("{0:.2f}".format(round(m[p],2)), 1, pygame.Color(0,0,0)) + self.window.blit(text, mp) + # Draw a square where the mouse is + # This also serves to indicate who's turn it is + + if isinstance(game, GameThread): + with game.lock: + turn = game.state["turn"] + else: + turn = None + + if isinstance(turn, HumanPlayer): + mp = [self.grid_sz[i] * int(pygame.mouse.get_pos()[i] / self.grid_sz[i]) for i in range(2)] + square_img.fill(pygame.Color(0,0,255,128)) + if turn.colour == "white": + c = pygame.Color(255,255,255) + else: + c = pygame.Color(0,0,0) + pygame.draw.rect(square_img, c, (0,0,self.grid_sz[0], self.grid_sz[1]), self.grid_sz[0]/10) + self.window.blit(square_img, mp) + + # Message in a bottle + def message(self, string, pos = None, colour = None, font_size = 20): + #print "Drawing message..." + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size) + if colour == None: + colour = pygame.Color(0,0,0) + + text = font.render(string, 1, colour) + + + s = pygame.Surface((text.get_width(), text.get_height()), pygame.SRCALPHA) + s.fill(pygame.Color(128,128,128)) + + tmp = self.window.get_size() + + if pos == None: + pos = (tmp[0] / 2 - text.get_width() / 2, tmp[1] / 3 - text.get_height()) + else: + pos = (pos[0]*text.get_width() + tmp[0] / 2 - text.get_width() / 2, pos[1]*text.get_height() + tmp[1] / 3 - text.get_height()) + + + rect = (pos[0], pos[1], text.get_width(), text.get_height()) + + pygame.draw.rect(self.window, pygame.Color(0,0,0), pygame.Rect(rect), 1) + self.window.blit(s, pos) + self.window.blit(text, pos) + + pygame.display.flip() + + def getstr(self, prompt = None): + s = pygame.Surface((self.window.get_width(), self.window.get_height())) + s.blit(self.window, (0,0)) + result = "" + + while True: + #print "LOOP" + if prompt != None: + self.message(prompt) + self.message(result, pos = (0, 1)) + + pygame.event.pump() + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return None + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_BACKSPACE: + result = result[0:len(result)-1] + self.window.blit(s, (0,0)) # Revert the display + continue + + + try: + if event.unicode == '\r': + return result + + result += str(event.unicode) + except: + continue + + + # Function to pick a button + def SelectButton(self, choices, prompt = None, font_size=20): + + #print "Select button called!" + self.board.display_grid(self.window, self.grid_sz) + if prompt != None: + self.message(prompt) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size) + targets = [] + sz = self.window.get_size() + + + for i in range(len(choices)): + c = choices[i] + + text = font.render(c, 1, pygame.Color(0,0,0)) + p = (sz[0] / 2 - (1.5*text.get_width())/2, sz[1] / 2 +(i-1)*text.get_height()+(i*2)) + targets.append((p[0], p[1], p[0] + 1.5*text.get_width(), p[1] + text.get_height())) + + while True: + mp =pygame.mouse.get_pos() + for i in range(len(choices)): + c = choices[i] + if mp[0] > targets[i][0] and mp[0] < targets[i][2] and mp[1] > targets[i][1] and mp[1] < targets[i][3]: + font_colour = pygame.Color(255,0,0) + box_colour = pygame.Color(0,0,255,128) + else: + font_colour = pygame.Color(0,0,0) + box_colour = pygame.Color(128,128,128) + + text = font.render(c, 1, font_colour) + s = pygame.Surface((text.get_width()*1.5, text.get_height()), pygame.SRCALPHA) + s.fill(box_colour) + pygame.draw.rect(s, (0,0,0), (0,0,1.5*text.get_width(), text.get_height()), self.grid_sz[0]/10) + s.blit(text, ((text.get_width()*1.5)/2 - text.get_width()/2 ,0)) + self.window.blit(s, targets[i][0:2]) + + + pygame.display.flip() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return None + elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: + for i in range(len(targets)): + t = targets[i] + if event.pos[0] > t[0] and event.pos[0] < t[2]: + if event.pos[1] > t[1] and event.pos[1] < t[3]: + return i + #print "Reject " + str(i) + str(event.pos) + " vs " + str(t) + + + # Function to choose between dedicated server or normal play + def SelectServer(self): + + choice = self.SelectButton(["Normal", "Join Eigenserver"],prompt="Game type?") + if choice == 0: + return None + choice = self.SelectButton(["progcomp.ucc", "other"], prompt="Address?") + if choice == 0: + return "progcomp.ucc.asn.au" + else: + return self.getstr(prompt = "Enter address:") + + # Function to pick players in a nice GUI way + def SelectPlayers(self, players = []): + + + #print "SelectPlayers called" + + missing = ["white", "black"] + for p in players: + missing.remove(p.colour) + + for colour in missing: + + + choice = self.SelectButton(["human", "agent", "network"],prompt = "Choose " + str(colour) + " player") + if choice == 0: + players.append(HumanPlayer("human", colour)) + elif choice == 1: + import inspect + internal_agents = inspect.getmembers(sys.modules[__name__], inspect.isclass) + internal_agents = [x for x in internal_agents if issubclass(x[1], InternalAgent)] + internal_agents.remove(('InternalAgent', InternalAgent)) + if len(internal_agents) > 0: + choice2 = self.SelectButton(["internal", "external"], prompt="Type of agent") + else: + choice2 = 1 + + if choice2 == 0: + agent = internal_agents[self.SelectButton(map(lambda e : e[0], internal_agents), prompt="Choose internal agent")] + players.append(agent[1](agent[0], colour)) + elif choice2 == 1: + try: + import Tkinter + from tkFileDialog import askopenfilename + root = Tkinter.Tk() # Need a root to make Tkinter behave + root.withdraw() # Some sort of magic incantation + path = askopenfilename(parent=root, initialdir="../agents",title= +'Choose an agent.') + if path == "": + return self.SelectPlayers() + players.append(make_player(path, colour)) + except: + + p = None + while p == None: + self.board.display_grid(self.window, self.grid_sz) + pygame.display.flip() + path = self.getstr(prompt = "Enter path:") + if path == None: + return None + + if path == "": + return self.SelectPlayers() + + try: + p = make_player(path, colour) + except: + self.board.display_grid(self.window, self.grid_sz) + pygame.display.flip() + self.message("Invalid path!") + time.sleep(1) + p = None + players.append(p) + elif choice == 1: + address = "" + while address == "": + self.board.display_grid(self.window, self.grid_sz) + + address = self.getstr(prompt = "Address? (leave blank for server)") + if address == None: + return None + if address == "": + address = None + continue + try: + map(int, address.split(".")) + except: + self.board.display_grid(self.window, self.grid_sz) + self.message("Invalid IPv4 address!") + address = "" + + players.append(NetworkReceiver(colour, address)) + else: + return None + #print str(self) + ".SelectPlayers returns " + str(players) + return players + + + +# --- graphics.py --- # +def dedicated_server(): + global log_files + + max_games = 5 + games = [] + gameID = 0 + while True: + # Get players + gameID += 1 + log("Getting clients...") + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(("0.0.0.0", 4562)) + s.listen(2) + ss = s.accept() + + log("Got white player") + + args = ["python", "qchess.py", "--no-graphics", "@network::"+str(4600+2*len(games)), "@network::"+str(4600+2*len(games))] + if len(log_files) != 0: + for l in log_files: + if l.name == "": + args.append("--log") + else: + args.append("--log="+str(l.name)+"_"+str(gameID)) + + g = subprocess.Popen(args, stdout=subprocess.PIPE) + games.append(g) + + time.sleep(0.5) + ss[0].send("white " + str(4600 + 2*(len(games)-1))) + ss[0].shutdown(socket.SHUT_RD) + ss[0].close() + + time.sleep(0.5) + ss = s.accept() + + log("Got black player") + + time.sleep(0.5) + ss[0].send("black " + str(4600 + 2*(len(games)-1))) + ss[0].shutdown(socket.SHUT_RD) + ss[0].close() + + s.shutdown(socket.SHUT_RDWR) + s.close() + + + while len(games) > max_games: + #log("Too many games; waiting for game to finish...") + ready = select.select(map(lambda e : e.stdout, games),[], []) + for r in ready[0]: + s = r.readline().strip(" \r\n").split(" ") + if s[0] == "white" or s[0] == "black": + for g in games[:]: + if g.stdout == r: + log("Game " + str(g) + " has finished") + games.remove(g) + + return 0 + +def client(addr): + + + + s = socket.socket() + s.connect((addr, 4562)) + + [colour,port] = s.recv(1024).strip(" \r\n").split(" ") + + #debug("Colour: " + colour + ", port: " + port) + + s.shutdown(socket.SHUT_RDWR) + s.close() + + if colour == "white": + p = subprocess.Popen(["python", "qchess.py", "@human", "@network:"+addr+":"+port]) + else: + p = subprocess.Popen(["python", "qchess.py", "@network:"+addr+":"+port, "@human"]) + p.wait() + return 0# --- server.py --- # +#!/usr/bin/python -u + +# Do you know what the -u does? It unbuffers stdin and stdout +# I can't remember why, but last year things broke without that + +""" + UCC::Progcomp 2013 Quantum Chess game + @author Sam Moore [SZM] "matches" + @copyright The University Computer Club, Incorporated + (ie: You can copy it for not for profit purposes) +""" + +# system python modules or whatever they are called +import sys +import os +import time + +turn_delay = 0.5 +sleep_timeout = None +[game, graphics] = [None, None] + +def make_player(name, colour): + if name[0] == '@': + if name[1:] == "human": + return HumanPlayer(name, colour) + s = name[1:].split(":") + if s[0] == "network": + ip = None + port = 4562 + #print str(s) + if len(s) > 1: + if s[1] != "": + ip = s[1] + if len(s) > 2: + port = int(s[2]) + + if ip == None: + if colour == "black": + port += 1 + elif colour == "white": + port += 1 + + return NetworkPlayer(colour, Network((ip, port)), None) + if s[0] == "internal": + + import inspect + internal_agents = inspect.getmembers(sys.modules[__name__], inspect.isclass) + internal_agents = [x for x in internal_agents if issubclass(x[1], InternalAgent)] + internal_agents.remove(('InternalAgent', InternalAgent)) + + if len(s) != 2: + sys.stderr.write(sys.argv[0] + " : '@internal' should be followed by ':' and an agent name\n") + sys.stderr.write(sys.argv[0] + " : Choices are: " + str(map(lambda e : e[0], internal_agents)) + "\n") + return None + + for a in internal_agents: + if s[1] == a[0]: + return a[1](name, colour) + + sys.stderr.write(sys.argv[0] + " : Can't find an internal agent matching \"" + s[1] + "\"\n") + sys.stderr.write(sys.argv[0] + " : Choices are: " + str(map(lambda e : e[0], internal_agents)) + "\n") + return None + + + else: + return ExternalAgent(name, colour) + + + +# The main function! It does the main stuff! +def main(argv): + + # Apparently python will silently treat things as local unless you do this + # Anyone who says "You should never use a global variable" can die in a fire + global game + global graphics + + global turn_delay + global agent_timeout + global log_files + global src_file + global graphics_enabled + global always_reveal_states + global sleep_timeout + + + server_addr = None + + max_moves = None + src_file = None + + style = "quantum" + colour = "white" + + # Get the important warnings out of the way + if platform.system() == "Windows": + sys.stderr.write(sys.argv[0] + " : Warning - You are using " + platform.system() + "\n") + if platform.release() == "Vista": + sys.stderr.write(sys.argv[0] + " : God help you.\n") + + + players = [] + i = 0 + while i < len(argv)-1: + i += 1 + arg = argv[i] + if arg[0] != '-': + p = make_player(arg, colour) + if not isinstance(p, Player): + sys.stderr.write(sys.argv[0] + " : Fatal error creating " + colour + " player\n") + return 100 + players.append(p) + if colour == "white": + colour = "black" + elif colour == "black": + pass + else: + sys.stderr.write(sys.argv[0] + " : Too many players (max 2)\n") + continue + + # Option parsing goes here + if arg[1] == '-' and arg[2:] == "classical": + style = "classical" + elif arg[1] == '-' and arg[2:] == "quantum": + style = "quantum" + elif arg[1] == '-' and arg[2:] == "reveal": + always_reveal_states = True + elif (arg[1] == '-' and arg[2:] == "graphics"): + graphics_enabled = True + elif (arg[1] == '-' and arg[2:] == "no-graphics"): + graphics_enabled = False + elif (arg[1] == '-' and arg[2:].split("=")[0] == "file"): + # Load game from file + if len(arg[2:].split("=")) == 1: + src_file = sys.stdin + else: + f = arg[2:].split("=")[1] + if f[0:7] == "http://": + src_file = HttpReplay(f) + else: + src_file = FileReplay(f.split(":")[0]) + + if len(f.split(":")) == 2: + max_moves = int(f.split(":")[1]) + + elif (arg[1] == '-' and arg[2:].split("=")[0] == "server"): + #debug("Server: " + str(arg[2:])) + if len(arg[2:].split("=")) <= 1: + server_addr = True + else: + server_addr = arg[2:].split("=")[1] + + elif (arg[1] == '-' and arg[2:].split("=")[0] == "log"): + # Log file + if len(arg[2:].split("=")) == 1: + log_files.append(LogFile(sys.stdout,"")) + else: + f = arg[2:].split("=")[1] + if f == "": + log_files.append(LogFile(sys.stdout, "")) + elif f[0] == '@': + log_files.append(ShortLog(f[1:])) + else: + log_files.append(LogFile(open(f, "w", 0), f)) + elif (arg[1] == '-' and arg[2:].split("=")[0] == "delay"): + # Delay + if len(arg[2:].split("=")) == 1: + turn_delay = 0 + else: + turn_delay = float(arg[2:].split("=")[1]) + + elif (arg[1] == '-' and arg[2:].split("=")[0] == "timeout"): + # Timeout + if len(arg[2:].split("=")) == 1: + agent_timeout = -1 + else: + agent_timeout = float(arg[2:].split("=")[1]) + elif (arg[1] == '-' and arg[2:].split("=")[0] == "blackout"): + # Screen saver delay + if len(arg[2:].split("=")) == 1: + sleep_timeout = -1 + else: + sleep_timeout = float(arg[2:].split("=")[1]) + + elif (arg[1] == '-' and arg[2:] == "help"): + # Help + os.system("less data/help.txt") # The best help function + return 0 + + # Dedicated server? + + #debug("server_addr = " + str(server_addr)) + + if server_addr != None: + if server_addr == True: + return dedicated_server() + else: + return client(server_addr) + + + # Create the board + + # Construct a GameThread! Make it global! Damn the consequences! + + if src_file != None: + # Hack to stop ReplayThread from exiting + #if len(players) == 0: + # players = [HumanPlayer("dummy", "white"), HumanPlayer("dummy", "black")] + + # Normally the ReplayThread exits if there are no players + # TODO: Decide which behaviour to use, and fix it + end = (len(players) == 0) + if end: + players = [Player("dummy", "white"), Player("dummy", "black")] + elif len(players) != 2: + sys.stderr.write(sys.argv[0] + " : Usage " + sys.argv[0] + " white black\n") + if graphics_enabled: + sys.stderr.write(sys.argv[0] + " : (You won't get a GUI, because --file was used, and the author is lazy)\n") + return 44 + game = ReplayThread(players, src_file, end=end, max_moves=max_moves) + else: + board = Board(style) + board.max_moves = max_moves + game = GameThread(board, players) + + + + + # Initialise GUI + if graphics_enabled == True: + try: + graphics = GraphicsThread(game.board, grid_sz = [64,64]) # Construct a GraphicsThread! + + graphics.sleep_timeout = sleep_timeout + + except Exception,e: + graphics = None + sys.stderr.write(sys.argv[0] + " : Got exception trying to initialise graphics\n"+str(e.message)+"\nDisabled graphics\n") + graphics_enabled = False + + # If there are no players listed, display a nice pretty menu + if len(players) != 2: + if graphics != None: + + server_addr = graphics.SelectServer() + if server_addr != None: + if server_addr == True: + return dedicated_server() + else: + return client(server_addr) + + players = graphics.SelectPlayers(players) + else: + sys.stderr.write(sys.argv[0] + " : Usage " + sys.argv[0] + " white black\n") + return 44 + + # If there are still no players, quit + if players == None or len(players) != 2: + sys.stderr.write(sys.argv[0] + " : Graphics window closed before players chosen\n") + return 45 + + old = players[:] + for p in old: + if isinstance(p, NetworkPlayer): + for i in range(len(old)): + if old[i] == p or isinstance(old[i], NetworkPlayer): + continue + players[i] = NetworkPlayer(old[i].colour, p.network, old[i]) + + for p in players: + #debug(str(p)) + if isinstance(p, NetworkPlayer): + p.board = game.board + if not p.network.connected: + if not p.network.server: + time.sleep(0.2) + p.network.connect() + + + # If using windows, select won't work; use horrible TimeoutPlayer hack + if agent_timeout > 0: + if platform.system() == "Windows": + for i in range(len(players)): + if isinstance(players[i], ExternalAgent) or isinstance(players[i], InternalAgent): + players[i] = TimeoutPlayer(players[i], agent_timeout) + + else: + warned = False + # InternalAgents get wrapped to an ExternalAgent when there is a timeout + # This is not confusing at all. + for i in range(len(players)): + if isinstance(players[i], InternalAgent): + players[i] = ExternalWrapper(players[i]) + + + + + + + + log_init(game.board, players) + + + if graphics != None: + game.start() # This runs in a new thread + graphics.run() + if game.is_alive(): + game.join() + + + error = game.error + graphics.error + else: + game.run() + error = game.error + + + for l in log_files: + l.close() + + if src_file != None and src_file != sys.stdin: + src_file.close() + + sys.stdout.write(game.final_result + "\n") + + return error + + + + + + + + +# This is how python does a main() function... +if __name__ == "__main__": + retcode = 0 + try: + retcode = main(sys.argv) + except KeyboardInterrupt: + sys.stderr.write(sys.argv[0] + " : Got KeyboardInterrupt. Stopping everything\n") + if isinstance(graphics, StoppableThread): + graphics.stop() + graphics.run() # Will clean up graphics because it is stopped, not run it (a bit dodgy) + + if isinstance(game, StoppableThread): + game.stop() + if game.is_alive(): + game.join() + retcode = 102 + #except Exception, e: + # sys.stderr.write(sys.argv[0] + " : " + e.message + "\n") + # retcode = 103 + + try: + sys.stdout.close() + except: + pass + try: + sys.stderr.close() + except: + pass + sys.exit(retcode) + + +# --- main.py --- # +# EOF - created from make on Sat Apr 20 12:19:31 WST 2013 diff --git a/agents/c++/agent++ b/agents/c++/agent++ old mode 100755 new mode 100644 diff --git a/agents/fortran/agent b/agents/fortran/agent old mode 100755 new mode 100644 diff --git a/agents/java/cough b/agents/java/cough old mode 100755 new mode 100644 diff --git a/agents/python/data b/agents/python/data deleted file mode 120000 index 75a080af8fdf43fc66d55d1102ce8d663b397ea0..0000000000000000000000000000000000000000 --- a/agents/python/data +++ /dev/null @@ -1 +0,0 @@ -../../qchess/data/ \ No newline at end of file diff --git a/agents/python/data/DejaVuSans.ttf b/agents/python/data/DejaVuSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..27cff476ef317e4666f7ffb77e82b9398a90825a Binary files /dev/null and b/agents/python/data/DejaVuSans.ttf differ diff --git a/agents/python/data/help.txt b/agents/python/data/help.txt new file mode 100644 index 0000000000000000000000000000000000000000..8911f3b2da55b93d35e516f01965349ee832c327 --- /dev/null +++ b/agents/python/data/help.txt @@ -0,0 +1,135 @@ +NAME + qchess.py - Play quantum chess + +SYNOPSIS + qchess.py [OPTIONS] [white] [black] + +DESCRIPTION + An implementation of Quantum Chess as originally described and implemented here: + http://research.cs.queensu.ca/Parallel/QuantumChess/QuantumChess.html + + Reimplemented for UCC::Progcomp 2013 + http://progcomp.ucc.asn.au + + IMPORTANT: + - This version does not implement castling or en passen rules. + - If a piece currently in a pawn state moves into the opposing back row, that state always becomes a queen. + - (The other state of the piece is unaffected). + + +ARGUMENTS + + If no arguments are given, a window should appear asking you to pick each player. + Then the game will commence using default values. + + white, black + Each of the two players in order. They need not be provided if graphics is enabled (default). + + Any arguments that do not begin with a hyphen (-) are treated as the player arguments in the order they appear. + + Player arguments that begin with '@' are treated as special players: + + @human + A human player; if graphics are enabled, this players turns are made through the GUI + + @network[:address] + A player over a network connection. + + For example, if black@host1 wants to play white@host2: + + black@host1:~$ ./qchess.py @network @human + white@host2:~$ ./qchess.py @human @network:host1 + + IMPORTANT: Only ONE of the games should give the other's address. + + @internal:name + An internal agent player + + These agents run within the qchess program (unless there is a timeout setting... never mind). + + Choices are: + + AgentRandom - Makes random moves only + + AgentBishop - Uses probability estimates and a min/max recursive (depth is only one) algorithm + - Will usually take a long time to run + +OPTIONS + + --help + Print this page + + --graphics + Enable the GUI + + If graphics are enabled (default), then the user will be prompted to choose any of the two players not supplied as arguments. + + --no-graphics + Disable the GUI + + + --reveal + If graphics are enabled, the two states for pieces will always be shown, regardless of whether both states have been revealed. + Note that this switch only affects the GUI and does not provide any information to agent players. + + If graphics are disabled, has no effect. + + --file[=filename][:events] + Replay a game saved in file, or read from stdin if no filename given + If a number of events is supplied, the game will advance that many events before stopping. + + If no players are given, the GUI will NOT ask for player selections. + The game will exit after the replay finishes. Events in the replay will be subject to the normal delay (see --delay). + + If black and white players are supplied, the game will continue using those players. + In this case, there will be no delays between events in the replay (the game starts at the end of the replay) + + (We hope that) this feature will be useful for comparing how different versions of an agent respond to the same situation. + + --log[=filename] + Log moves to a file or stdout if no filename given + + + + --delay[=time] + The game pauses between moves so that it can be followed by a human observer. + This option can be used to change the delay. If no time is given, the delay is disabled. + + If graphics are enabled (default), the delay is 0.5s by default. + If graphics are disabled, there is no delay unless this option is used. + + --timeout[=time] + Set the maximum time in seconds to wait before declaring an AI program unresponsive. + If no time is given, the timeout is disabled. + + By default the timeout is disabled. + + --blackout[=time] + Setting a blackout time will cause the display to become black if the mouse is not moved and no keys or buttons are pressed. + If no time is given, the blackout time is disabled. + + By default the blackout is disabled. + + This switch was introduced for entirely obscure purposes. + + --classical + If this option is used, the game will treat pieces "classically", ie: as in standard chess. + Note that the game does not enforce rules related to check and checkmate. + + --quantum + The game uses the quantum chess representation of pieces (default). + + +AUTHOR + Written for the UCC Programming Competition 2013 by Sam Moore. + UCC::Progcomp home page: http://progcomp.ucc.asn.au + +REPORTING BUGS + Report bugs to matches@ucc.asn.au + Join IRC channel #progcomp on irc://irc.ucc.asn.au + +COPYRIGHT + Copyright 2013 The University Computer Club, Inc. + + Contact committee@ucc.asn.au + diff --git a/agents/python/qchess.py b/agents/python/qchess.py deleted file mode 120000 index 35c6d6e8a5edf5bdf5173254d57a1c5500b7b59b..0000000000000000000000000000000000000000 --- a/agents/python/qchess.py +++ /dev/null @@ -1 +0,0 @@ -../../qchess/qchess.py \ No newline at end of file diff --git a/agents/python/qchess.py b/agents/python/qchess.py new file mode 100644 index 0000000000000000000000000000000000000000..8a3610bd9cfc1d3e4ba1af9565d070f9d645d6b8 --- /dev/null +++ b/agents/python/qchess.py @@ -0,0 +1,2837 @@ +#!/usr/bin/python -u +import random + +# I know using non-abreviated strings is inefficient, but this is python, who cares? +# Oh, yeah, this stores the number of pieces of each type in a normal chess game +piece_types = {"pawn" : 8, "bishop" : 2, "knight" : 2, "rook" : 2, "queen" : 1, "king" : 1, "unknown" : 0} + +# Class to represent a quantum chess piece +class Piece(): + def __init__(self, colour, x, y, types): + self.colour = colour # Colour (string) either "white" or "black" + self.x = x # x coordinate (0 - 8), none of this fancy 'a', 'b' shit here + self.y = y # y coordinate (0 - 8) + self.types = types # List of possible types the piece can be (should just be two) + self.current_type = "unknown" # Current type + self.choice = -1 # Index of the current type in self.types (-1 = unknown type) + + + self.last_state = None + + self.move_pattern = None + self.coverage = None + self.possible_moves = {} + + + def init_from_copy(self, c): + self.colour = c.colour + self.x = c.x + self.y = c.y + self.types = c.types[:] + self.current_type = c.current_type + self.choice = c.choice + + self.last_state = None + self.move_pattern = None + + + + # Make a string for the piece (used for debug) + def __str__(self): + return str(self.colour) + " " + str(self.current_type) + " " + str(self.types) + " at " + str(self.x) + ","+str(self.y) + + # Draw the piece in a pygame surface + def draw(self, window, grid_sz = [80,80], style="quantum"): + + # First draw the image corresponding to self.current_type + img = images[self.colour][self.current_type] + rect = img.get_rect() + if style == "classical": + offset = [-rect.width/2, -rect.height/2] + else: + offset = [-rect.width/2,-3*rect.height/4] + window.blit(img, (self.x * grid_sz[0] + grid_sz[0]/2 + offset[0], self.y * grid_sz[1] + grid_sz[1]/2 + offset[1])) + + + if style == "classical": + return + + # Draw the two possible types underneath the current_type image + for i in range(len(self.types)): + if always_reveal_states == True or self.types[i][0] != '?': + if self.types[i][0] == '?': + img = small_images[self.colour][self.types[i][1:]] + else: + img = small_images[self.colour][self.types[i]] + else: + img = small_images[self.colour]["unknown"] # If the type hasn't been revealed, show a placeholder + + + rect = img.get_rect() + offset = [-rect.width/2,-rect.height/2] + + if i == 0: + target = (self.x * grid_sz[0] + grid_sz[0]/5 + offset[0], self.y * grid_sz[1] + 3*grid_sz[1]/4 + offset[1]) + else: + target = (self.x * grid_sz[0] + 4*grid_sz[0]/5 + offset[0], self.y * grid_sz[1] + 3*grid_sz[1]/4 + offset[1]) + + window.blit(img, target) # Blit shit + + # Collapses the wave function! + def select(self): + if self.current_type == "unknown" or not self.choice in [0,1]: + self.choice = random.randint(0,1) + if self.types[self.choice][0] == '?': + self.types[self.choice] = self.types[self.choice][1:] + self.current_type = self.types[self.choice] + return self.choice + + # Uncollapses (?) the wave function! + def deselect(self): + #print "Deselect called" + if (self.x + self.y) % 2 != 0: + if (self.types[0] != self.types[1]) or (self.types[0][0] == '?' or self.types[1][0] == '?'): + self.current_type = "unknown" + self.choice = -1 + else: + self.choice = 0 # Both the two types are the same + + # The sad moment when you realise that you do not understand anything about a subject you studied for 4 years... +# --- piece.py --- # +[w,h] = [8,8] # Width and height of board(s) + +always_reveal_states = False + +# Class to represent a quantum chess board +class Board(): + # Initialise; if master=True then the secondary piece types are assigned + # Otherwise, they are left as unknown + # So you can use this class in Agent programs, and fill in the types as they are revealed + def __init__(self, style="agent"): + self.style = style + self.pieces = {"white" : [], "black" : []} + self.grid = [[None] * w for _ in range(h)] # 2D List (you can get arrays in python, somehow, but they scare me) + self.unrevealed_types = {"white" : piece_types.copy(), "black" : piece_types.copy()} + self.king = {"white" : None, "black" : None} # We need to keep track of the king, because he is important + self.max_moves = None + self.moves = 0 + self.move_stack = [] + for c in ["black", "white"]: + del self.unrevealed_types[c]["unknown"] + + if style == "empty": + return + + # Add all the pieces with known primary types + for i in range(0, 2): + + s = ["black", "white"][i] + c = self.pieces[s] + y = [0, h-1][i] + + c.append(Piece(s, 0, y, ["rook"])) + c.append(Piece(s, 1, y, ["knight"])) + c.append(Piece(s, 2, y, ["bishop"])) + k = Piece(s, 3, y, ["king", "king"]) # There can only be one ruler! + k.current_type = "king" + self.king[s] = k + c.append(k) + c.append(Piece(s, 4, y, ["queen"])) # Apparently he may have multiple wives though. + c.append(Piece(s, 5, y, ["bishop"])) + c.append(Piece(s, 6, y, ["knight"])) + c.append(Piece(s, 7, y, ["rook"])) + + if y == 0: + y += 1 + else: + y -= 1 + + # Lots of pawn + for x in range(0, w): + c.append(Piece(s, x, y, ["pawn"])) + + types_left = {} + types_left.update(piece_types) + del types_left["king"] # We don't want one of these randomly appearing (although it might make things interesting...) + del types_left["unknown"] # We certainly don't want these! + for piece in c: + # Add to grid + self.grid[piece.x][piece.y] = piece + + if len(piece.types) > 1: + continue + if style == "agent": # Assign placeholder "unknown" secondary type + piece.types.append("unknown") + continue + + elif style == "quantum": + # The master allocates the secondary types + choice = types_left.keys()[random.randint(0, len(types_left.keys())-1)] + types_left[choice] -= 1 + if types_left[choice] <= 0: + del types_left[choice] + piece.types.append('?' + choice) + elif style == "classical": + piece.types.append(piece.types[0]) + piece.current_type = piece.types[0] + piece.choice = 0 + + def clone(self): + newboard = Board(master = False) + newpieces = newboard.pieces["white"] + newboard.pieces["black"] + mypieces = self.pieces["white"] + self.pieces["black"] + + for i in range(len(mypieces)): + newpieces[i].init_from_copy(mypieces[i]) + + # Reset the board from a string + def reset_board(self, s): + self.pieces = {"white" : [], "black" : []} + self.king = {"white" : None, "black" : None} + self.grid = [[None] * w for _ in range(h)] + for x in range(w): + for y in range(h): + self.grid[x][y] = None + + for line in s.split("\n"): + if line == "": + continue + if line[0] == "#": + continue + + tokens = line.split(" ") + [x, y] = map(int, tokens[len(tokens)-1].split(",")) + current_type = tokens[1] + types = map(lambda e : e.strip(" '[],"), line.split('[')[1].split(']')[0].split(',')) + + target = Piece(tokens[0], x, y, types) + target.current_type = current_type + + try: + target.choice = types.index(current_type) + except: + target.choice = -1 + + self.pieces[tokens[0]].append(target) + if target.current_type == "king": + self.king[tokens[0]] = target + + self.grid[x][y] = target + + + def display_grid(self, window = None, grid_sz = [80,80]): + if window == None: + return # I was considering implementing a text only display, then I thought "Fuck that" + + # The indentation is getting seriously out of hand... + for x in range(0, w): + for y in range(0, h): + if (x + y) % 2 == 0: + c = pygame.Color(200,200,200) + else: + c = pygame.Color(64,64,64) + pygame.draw.rect(window, c, (x*grid_sz[0], y*grid_sz[1], (x+1)*grid_sz[0], (y+1)*grid_sz[1])) + + def display_pieces(self, window = None, grid_sz = [80,80]): + if window == None: + return + for p in self.pieces["white"] + self.pieces["black"]: + p.draw(window, grid_sz, self.style) + + # Draw the board in a pygame window + def display(self, window = None): + self.display_grid(window) + self.display_pieces(window) + + + + + def verify(self): + for x in range(w): + for y in range(h): + if self.grid[x][y] == None: + continue + if (self.grid[x][y].x != x or self.grid[x][y].y != y): + raise Exception(sys.argv[0] + ": MISMATCH " + str(self.grid[x][y]) + " should be at " + str(x) + "," + str(y)) + + # Select a piece on the board (colour is the colour of whoever is doing the selecting) + def select(self, x,y, colour=None): + if not self.on_board(x, y): # Get on board everyone! + raise Exception("BOUNDS " + str(x) + ","+str(y)) + + piece = self.grid[x][y] + if piece == None: + raise Exception("EMPTY") + + if colour != None and piece.colour != colour: + raise Exception("COLOUR " + str(piece.colour) + " not " + str(colour)) + + # I'm not quite sure why I made this return a string, but screw logical design + return str(x) + " " + str(y) + " " + str(piece.select()) + " " + str(piece.current_type) + + + # Update the board when a piece has been selected + # "type" is apparently reserved, so I'll use "state" + def update_select(self, x, y, type_index, state, sanity=True, deselect=True): + #debug(str(self) + " update_select called") + piece = self.grid[x][y] + if piece.types[type_index] == "unknown": + if not state in self.unrevealed_types[piece.colour].keys() and sanity == True: + raise Exception("SANITY: Too many " + piece.colour + " " + state + "s") + self.unrevealed_types[piece.colour][state] -= 1 + if self.unrevealed_types[piece.colour][state] <= 0: + del self.unrevealed_types[piece.colour][state] + + piece.types[type_index] = state + piece.current_type = state + + if deselect == True and len(self.possible_moves(piece)) <= 0: + piece.deselect() # Piece can't move; deselect it + + # Piece needs to recalculate moves + piece.possible_moves = None + + # Update the board when a piece has been moved + def update_move(self, x, y, x2, y2, sanity=True): + #debug(str(self) + " update_move called \""+str(x)+ " " + str(y) + " -> " + str(x2) + " " + str(y2) + "\"") + piece = self.grid[x][y] + #print "Moving " + str(x) + "," + str(y) + " to " + str(x2) + "," + str(y2) + "; possible_moves are " + str(self.possible_moves(piece)) + + if not [x2,y2] in self.possible_moves(piece) and sanity == True: + raise Exception("ILLEGAL move " + str(x2)+","+str(y2)) + + self.grid[x][y] = None + taken = self.grid[x2][y2] + if taken != None: + if taken.current_type == "king": + self.king[taken.colour] = None + self.pieces[taken.colour].remove(taken) + self.grid[x2][y2] = piece + piece.x = x2 + piece.y = y2 + + # If the piece is a pawn, and it reaches the final row, it becomes a queen + # I know you are supposed to get a choice + # But that would be effort + if piece.current_type == "pawn" and ((piece.colour == "white" and piece.y == 0) or (piece.colour == "black" and piece.y == h-1)): + if self.style == "classical": + piece.types[0] = "queen" + piece.types[1] = "queen" + else: + piece.types[piece.choice] = "queen" + piece.current_type = "queen" + + piece.deselect() # Uncollapse (?) the wavefunction! + self.moves += 1 + + # All other pieces need to recalculate moves + for p in self.pieces["white"] + self.pieces["black"]: + p.possible_moves = None + + #self.verify() + + # Update the board from a string + # Guesses what to do based on the format of the string + def update(self, result, sanity=True, deselect=True): + #debug(str(self) + " update called \""+str(result)+"\"") + # String always starts with 'x y' + try: + s = result.split(" ") + [x,y] = map(int, s[0:2]) + except: + raise Exception("GIBBERISH \""+ str(result) + "\"") # Raise expectations + + piece = self.grid[x][y] + if piece == None and sanity == True: + raise Exception("EMPTY " + str(x) + " " + str(y)) + + # If a piece is being moved, the third token is '->' + # We could get away with just using four integers, but that wouldn't look as cool + if "->" in s: + # Last two tokens are the destination + try: + [x2,y2] = map(int, s[3:]) + except: + raise Exception("GIBBERISH \"" + str(result) + "\"") # Raise the alarm + + # Move the piece (take opponent if possible) + self.update_move(x, y, x2, y2, sanity) + + else: + # Otherwise we will just assume a piece has been selected + try: + type_index = int(s[2]) # We need to know which of the two types the piece is in; that's the third token + state = s[3] # The last token is a string identifying the type + except: + raise Exception("GIBBERISH \"" + result + "\"") # Throw a hissy fit + + + # Select the piece + self.update_select(x, y, type_index, state, sanity=sanity, deselect=deselect) + + return result + + # Gets each piece that could reach the given square and the probability that it could reach that square + # Will include allied pieces that defend the attacker + def coverage(self, x, y, colour = None, reject_allied = True): + result = {} + + if colour == None: + pieces = self.pieces["white"] + self.pieces["black"] + else: + pieces = self.pieces[colour] + + for p in pieces: + prob = self.probability_grid(p, reject_allied)[x][y] + if prob > 0: + result.update({p : prob}) + + #self.verify() + return result + + + + + + # Associates each square with a probability that the piece could move into it + # Look, I'm doing all the hard work for you here... + def probability_grid(self, p, reject_allied = True): + + result = [[0.0] * w for _ in range(h)] + if not isinstance(p, Piece): + return result + + if p.current_type != "unknown": + #sys.stderr.write(sys.argv[0] + ": " + str(p) + " moves " + str(self.possible_moves(p, reject_allied)) + "\n") + for point in self.possible_moves(p, reject_allied): + result[point[0]][point[1]] = 1.0 + return result + + + for i in range(len(p.types)): + t = p.types[i] + prob = 1.0 / float(len(p.types)) + if t == "unknown" or p.types[i][0] == '?': + total_types = 0 + for t2 in self.unrevealed_types[p.colour].keys(): + total_types += self.unrevealed_types[p.colour][t2] + + for t2 in self.unrevealed_types[p.colour].keys(): + prob2 = float(self.unrevealed_types[p.colour][t2]) / float(total_types) + #p.current_type = t2 + for point in self.possible_moves(p, reject_allied, state=t2): + result[point[0]][point[1]] += prob2 * prob + + else: + #p.current_type = t + for point in self.possible_moves(p, reject_allied, state=t): + result[point[0]][point[1]] += prob + + #self.verify() + #p.current_type = "unknown" + return result + + def prob_is_type(self, p, state): + prob = 0.5 + result = 0 + for i in range(len(p.types)): + t = p.types[i] + if t == state: + result += prob + continue + if t == "unknown" or p.types[i][0] == '?': + total_prob = 0 + for t2 in self.unrevealed_types[p.colour].keys(): + total_prob += self.unrevealed_types[p.colour][t2] + for t2 in self.unrevealed_types[p.colour].keys(): + if t2 == state: + result += prob * float(self.unrevealed_types[p.colour][t2]) / float(total_prob) + + + + # Get all squares that the piece could move into + # This is probably inefficient, but I looked at some sample chess games and they seem to actually do things this way + # reject_allied indicates whether squares occupied by allied pieces will be removed + # (set to false to check for defense) + def possible_moves(self, p, reject_allied = True, state=None): + if p == None: + raise Exception("SANITY: No piece") + + + + if state != None and state != p.current_type: + old_type = p.current_type + p.current_type = state + result = self.possible_moves(p, reject_allied, state=None) + p.current_type = old_type + return result + + + + + result = [] + + + + if p.current_type == "unknown": + raise Exception("SANITY: Unknown state for piece: "+str(p)) + # The below commented out code causes things to break badly + #for t in p.types: + # if t == "unknown": + # continue + # p.current_type = t + # result += self.possible_moves(p) + #p.current_type = "unknown" + #return result + + if p.current_type == "king": + result = [[p.x-1,p.y],[p.x+1,p.y],[p.x,p.y-1],[p.x,p.y+1], [p.x-1,p.y-1],[p.x-1,p.y+1],[p.x+1,p.y-1],[p.x+1,p.y+1]] + elif p.current_type == "queen": + for d in [[-1,0],[1,0],[0,-1],[0,1],[-1,-1],[-1,1],[1,-1],[1,1]]: + result += self.scan(p.x, p.y, d[0], d[1]) + elif p.current_type == "bishop": + for d in [[-1,-1],[-1,1],[1,-1],[1,1]]: # There's a reason why bishops move diagonally + result += self.scan(p.x, p.y, d[0], d[1]) + elif p.current_type == "rook": + for d in [[-1,0],[1,0],[0,-1],[0,1]]: + result += self.scan(p.x, p.y, d[0], d[1]) + elif p.current_type == "knight": + # I would use two lines, but I'm not sure how python likes that + result = [[p.x-2, p.y-1], [p.x-2, p.y+1], [p.x+2, p.y-1], [p.x+2,p.y+1], [p.x-1,p.y-2], [p.x-1, p.y+2],[p.x+1,p.y-2],[p.x+1,p.y+2]] + elif p.current_type == "pawn": + if p.colour == "white": + + # Pawn can't move forward into occupied square + if self.on_board(p.x, p.y-1) and self.grid[p.x][p.y-1] == None: + result = [[p.x,p.y-1]] + for f in [[p.x-1,p.y-1],[p.x+1,p.y-1]]: + if not self.on_board(f[0], f[1]): + continue + if self.grid[f[0]][f[1]] != None: # Pawn can take diagonally + result.append(f) + if p.y == h-2: + # Slightly embarrassing if the pawn jumps over someone on its first move... + if self.grid[p.x][p.y-1] == None and self.grid[p.x][p.y-2] == None: + result.append([p.x, p.y-2]) + else: + # Vice versa for the black pawn + if self.on_board(p.x, p.y+1) and self.grid[p.x][p.y+1] == None: + result = [[p.x,p.y+1]] + + for f in [[p.x-1,p.y+1],[p.x+1,p.y+1]]: + if not self.on_board(f[0], f[1]): + continue + if self.grid[f[0]][f[1]] != None: + #sys.stderr.write(sys.argv[0] + " : "+str(p) + " can take " + str(self.grid[f[0]][f[1]]) + "\n") + result.append(f) + if p.y == 1: + if self.grid[p.x][p.y+1] == None and self.grid[p.x][p.y+2] == None: + result.append([p.x, p.y+2]) + + #sys.stderr.write(sys.argv[0] + " : possible_moves for " + str(p) + " " + str(result) + "\n") + + # Remove illegal moves + # Note: The result[:] creates a copy of result, so that the result.remove calls don't fuck things up + for point in result[:]: + + if (point[0] < 0 or point[0] >= w) or (point[1] < 0 or point[1] >= h): + result.remove(point) # Remove locations outside the board + continue + g = self.grid[point[0]][point[1]] + + if g != None and (g.colour == p.colour and reject_allied == True): + result.remove(point) # Remove allied pieces + + #self.verify() + + p.possible_moves = result + return result + + + # Scans in a direction until it hits a piece, returns all squares in the line + # (includes the final square (which contains a piece), but not the original square) + def scan(self, x, y, vx, vy): + p = [] + + xx = x + yy = y + while True: + xx += vx + yy += vy + if not self.on_board(xx, yy): + break + if not [xx,yy] in p: + p.append([xx, yy]) + g = self.grid[xx][yy] + if g != None: + return p + + return p + + # Returns "white", "black" or "DRAW" if the game should end + def end_condition(self): + if self.king["white"] == None: + if self.king["black"] == None: + return "DRAW" # This shouldn't happen + return "black" + elif self.king["black"] == None: + return "white" + elif len(self.pieces["white"]) == 1 and len(self.pieces["black"]) == 1: + return "DRAW" + elif self.max_moves != None and self.moves > self.max_moves: + return "DRAW" + return None + + + # I typed the full statement about 30 times before writing this function... + def on_board(self, x, y): + return (x >= 0 and x < w) and (y >= 0 and y < h) + + # Pushes a move temporarily + def push_move(self, piece, x, y): + target = self.grid[x][y] + self.move_stack.append([piece, target, piece.x, piece.y, x, y]) + [piece.x, piece.y] = [x, y] + self.grid[x][y] = piece + self.grid[piece.x][piece.y] = None + + for p in self.pieces["white"] + self.pieces["black"]: + p.possible_moves = None + + # Restore move + def pop_move(self): + #print str(self.move_stack) + [piece, target, x1, y1, x2, y2] = self.move_stack[len(self.move_stack)-1] + self.move_stack = self.move_stack[:-1] + piece.x = x1 + piece.y = y1 + self.grid[x1][y1] = piece + if target != None: + target.x = x2 + target.y = y2 + self.grid[x2][y2] = target + + for p in self.pieces["white"] + self.pieces["black"]: + p.possible_moves = None + +# --- board.py --- # +import subprocess +import select +import platform +import re + +agent_timeout = -1.0 # Timeout in seconds for AI players to make moves + # WARNING: Won't work for windows based operating systems + +if platform.system() == "Windows": + agent_timeout = -1 # Hence this + +# A player who can't play +class Player(): + def __init__(self, name, colour): + self.name = name + self.colour = colour + + def update(self, result): + return result + + def reset_board(self, s): + pass + + def __str__(self): + return self.name + "<"+str(self.colour)+">" + + def base_player(self): + return self + +# Player that runs from another process +class ExternalAgent(Player): + + + def __init__(self, name, colour): + Player.__init__(self, name, colour) + self.p = subprocess.Popen(name,bufsize=0,stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True,universal_newlines=True) + + self.send_message(colour) + + def send_message(self, s): + if agent_timeout > 0.0: + ready = select.select([], [self.p.stdin], [], agent_timeout)[1] + else: + ready = [self.p.stdin] + if self.p.stdin in ready: + #sys.stderr.write("Writing \'" + s + "\' to " + str(self.p) + "\n") + try: + self.p.stdin.write(s + "\n") + except: + raise Exception("UNRESPONSIVE") + else: + raise Exception("TIMEOUT") + + def get_response(self): + if agent_timeout > 0.0: + ready = select.select([self.p.stdout], [], [], agent_timeout)[0] + else: + ready = [self.p.stdout] + if self.p.stdout in ready: + #sys.stderr.write("Reading from " + str(self.p) + " 's stdout...\n") + try: + result = self.p.stdout.readline().strip(" \t\r\n") + #sys.stderr.write("Read \'" + result + "\' from " + str(self.p) + "\n") + return result + except: # Exception, e: + raise Exception("UNRESPONSIVE") + else: + raise Exception("TIMEOUT") + + def select(self): + + self.send_message("SELECTION?") + line = self.get_response() + + try: + m = re.match("\s*(\d+)\s+(\d+)\s*", line) + result = map(int, [m.group(1), m.group(2)]) + except: + raise Exception("GIBBERISH \"" + str(line) + "\"") + return result + + def update(self, result): + #print "Update " + str(result) + " called for AgentPlayer" + self.send_message(result) + return result + + def get_move(self): + + self.send_message("MOVE?") + line = self.get_response() + + try: + m = re.match("\s*(\d+)\s+(\d+)\s*", line) + result = map(int, [m.group(1), m.group(2)]) + + except: + raise Exception("GIBBERISH \"" + str(line) + "\"") + return result + + def reset_board(self, s): + self.send_message("BOARD") + for line in s.split("\n"): + self.send_message(line.strip(" \r\n")) + self.send_message("END BOARD") + + def quit(self, final_result): + try: + self.send_message("QUIT " + final_result) + except: + self.p.kill() + +# So you want to be a player here? +class HumanPlayer(Player): + def __init__(self, name, colour): + Player.__init__(self, name, colour) + + # Select your preferred account + def select(self): + if isinstance(graphics, GraphicsThread): + # Basically, we let the graphics thread do some shit and then return that information to the game thread + graphics.cond.acquire() + # We wait for the graphics thread to select a piece + while graphics.stopped() == False and graphics.state["select"] == None: + graphics.cond.wait() # The difference between humans and machines is that humans sleep + select = graphics.state["select"] + + + graphics.cond.release() + if graphics.stopped(): + return [-1,-1] + return [select.x, select.y] + else: + # Since I don't display the board in this case, I'm not sure why I filled it in... + while True: + sys.stdout.write("SELECTION?\n") + try: + p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + except: + sys.stderr.write("ILLEGAL GIBBERISH\n") + continue + # It's your move captain + def get_move(self): + if isinstance(graphics, GraphicsThread): + graphics.cond.acquire() + while graphics.stopped() == False and graphics.state["dest"] == None: + graphics.cond.wait() + graphics.cond.release() + + return graphics.state["dest"] + else: + + while True: + sys.stdout.write("MOVE?\n") + try: + p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + except: + sys.stderr.write("ILLEGAL GIBBERISH\n") + continue + + # Are you sure you want to quit? + def quit(self, final_result): + if graphics == None: + sys.stdout.write("QUIT " + final_result + "\n") + + # Completely useless function + def update(self, result): + if isinstance(graphics, GraphicsThread): + pass + else: + sys.stdout.write(result + "\n") + return result + + +# Default internal player (makes random moves) +class InternalAgent(Player): + def __init__(self, name, colour): + Player.__init__(self, name, colour) + self.choice = None + + self.board = Board(style = "agent") + + + + def update(self, result): + + self.board.update(result) + #self.board.verify() + return result + + def reset_board(self, s): + self.board.reset_board(s) + + def quit(self, final_result): + pass + +class AgentRandom(InternalAgent): + def __init__(self, name, colour): + InternalAgent.__init__(self, name, colour) + + def select(self): + while True: + self.choice = self.board.pieces[self.colour][random.randint(0, len(self.board.pieces[self.colour])-1)] + all_moves = [] + # Check that the piece has some possibility to move + tmp = self.choice.current_type + if tmp == "unknown": # For unknown pieces, try both types + for t in self.choice.types: + if t == "unknown": + continue + self.choice.current_type = t + all_moves += self.board.possible_moves(self.choice) + else: + all_moves = self.board.possible_moves(self.choice) + self.choice.current_type = tmp + if len(all_moves) > 0: + break + return [self.choice.x, self.choice.y] + + def get_move(self): + moves = self.board.possible_moves(self.choice) + move = moves[random.randint(0, len(moves)-1)] + return move + + +# Terrible, terrible hacks + +def run_agent(agent): + #sys.stderr.write(sys.argv[0] + " : Running agent " + str(agent) + "\n") + while True: + line = sys.stdin.readline().strip(" \r\n") + if line == "SELECTION?": + #sys.stderr.write(sys.argv[0] + " : Make selection\n") + [x,y] = agent.select() # Gets your agent's selection + #sys.stderr.write(sys.argv[0] + " : Selection was " + str(agent.choice) + "\n") + sys.stdout.write(str(x) + " " + str(y) + "\n") + elif line == "MOVE?": + #sys.stderr.write(sys.argv[0] + " : Make move\n") + [x,y] = agent.get_move() # Gets your agent's move + sys.stdout.write(str(x) + " " + str(y) + "\n") + elif line.split(" ")[0] == "QUIT": + #sys.stderr.write(sys.argv[0] + " : Quitting\n") + agent.quit(" ".join(line.split(" ")[1:])) # Quits the game + break + elif line.split(" ")[0] == "BOARD": + s = "" + line = sys.stdin.readline().strip(" \r\n") + while line != "END BOARD": + s += line + "\n" + line = sys.stdin.readline().strip(" \r\n") + agent.board.reset_board(s) + + else: + agent.update(line) # Updates agent.board + return 0 + + +# Sort of works? + +class ExternalWrapper(ExternalAgent): + def __init__(self, agent): + run = "python -u -c \"import sys;import os;from qchess import *;agent = " + agent.__class__.__name__ + "('" + agent.name + "','"+agent.colour+"');sys.stdin.readline();sys.exit(run_agent(agent))\"" + # str(run) + ExternalAgent.__init__(self, run, agent.colour) + + + +# --- player.py --- # +# A sample agent + + +class AgentBishop(AgentRandom): # Inherits from AgentRandom (in qchess) + def __init__(self, name, colour): + InternalAgent.__init__(self, name, colour) + self.value = {"pawn" : 1, "bishop" : 3, "knight" : 3, "rook" : 5, "queen" : 9, "king" : 100, "unknown" : 4} + + self.aggression = 2.0 # Multiplier for scoring due to aggressive actions + self.defence = 1.0 # Multiplier for scoring due to defensive actions + + self.depth = 0 # Current depth + self.max_depth = 2 # Recurse this many times (for some reason, makes more mistakes when this is increased???) + self.recurse_for = -1 # Recurse for the best few moves each times (less than 0 = all moves) + + for p in self.board.pieces["white"] + self.board.pieces["black"]: + p.last_moves = None + p.selected_moves = None + + + + def get_value(self, piece): + if piece == None: + return 0.0 + return float(self.value[piece.types[0]] + self.value[piece.types[1]]) / 2.0 + + # Score possible moves for the piece + + def prioritise_moves(self, piece): + + #sys.stderr.write(sys.argv[0] + " : " + str(self) + " prioritise called for " + str(piece) + "\n") + + + + grid = self.board.probability_grid(piece) + #sys.stderr.write("\t Probability grid " + str(grid) + "\n") + moves = [] + for x in range(w): + for y in range(h): + if grid[x][y] < 0.3: # Throw out moves with < 30% probability + #sys.stderr.write("\tReject " + str(x) + "," + str(y) + " (" + str(grid[x][y]) + ")\n") + continue + + target = self.board.grid[x][y] + + + + + # Get total probability that the move is protected + self.board.push_move(piece, x, y) + + + + defenders = self.board.coverage(x, y, piece.colour, reject_allied = False) + d_prob = 0.0 + for d in defenders.keys(): + d_prob += defenders[d] + if len(defenders.keys()) > 0: + d_prob /= float(len(defenders.keys())) + + if (d_prob > 1.0): + d_prob = 1.0 + + # Get total probability that the move is threatened + attackers = self.board.coverage(x, y, opponent(piece.colour), reject_allied = False) + a_prob = 0.0 + for a in attackers.keys(): + a_prob += attackers[a] + if len(attackers.keys()) > 0: + a_prob /= float(len(attackers.keys())) + + if (a_prob > 1.0): + a_prob = 1.0 + + self.board.pop_move() + + + + # Score of the move + value = self.aggression * (1.0 + d_prob) * self.get_value(target) - self.defence * (1.0 - d_prob) * a_prob * self.get_value(piece) + + # Adjust score based on movement of piece out of danger + attackers = self.board.coverage(piece.x, piece.y, opponent(piece.colour)) + s_prob = 0.0 + for a in attackers.keys(): + s_prob += attackers[a] + if len(attackers.keys()) > 0: + s_prob /= float(len(attackers.keys())) + + if (s_prob > 1.0): + s_prob = 1.0 + value += self.defence * s_prob * self.get_value(piece) + + # Adjust score based on probability that the move is actually possible + moves.append([[x, y], grid[x][y] * value]) + + moves.sort(key = lambda e : e[1], reverse = True) + #sys.stderr.write(sys.argv[0] + ": Moves for " + str(piece) + " are " + str(moves) + "\n") + + piece.last_moves = moves + piece.selected_moves = None + + + + + return moves + + def select_best(self, colour): + + self.depth += 1 + all_moves = {} + for p in self.board.pieces[colour]: + self.choice = p # Temporarily pick that piece + m = self.prioritise_moves(p) + if len(m) > 0: + all_moves.update({p : m[0]}) + + if len(all_moves.items()) <= 0: + return None + + + opts = all_moves.items() + opts.sort(key = lambda e : e[1][1], reverse = True) + + if self.depth >= self.max_depth: + self.depth -= 1 + return list(opts[0]) + + if self.recurse_for >= 0: + opts = opts[0:self.recurse_for] + #sys.stderr.write(sys.argv[0] + " : Before recurse, options are " + str(opts) + "\n") + + # Take the best few moves, and recurse + for choice in opts[0:self.recurse_for]: + [xx,yy] = [choice[0].x, choice[0].y] # Remember position + [nx,ny] = choice[1][0] # Target + [choice[0].x, choice[0].y] = [nx, ny] # Set position + target = self.board.grid[nx][ny] # Remember piece in spot + self.board.grid[xx][yy] = None # Remove piece + self.board.grid[nx][ny] = choice[0] # Replace with moving piece + + # Recurse + best_enemy_move = self.select_best(opponent(choice[0].colour)) + choice[1][1] -= best_enemy_move[1][1] / float(self.depth + 1.0) + + [choice[0].x, choice[0].y] = [xx, yy] # Restore position + self.board.grid[nx][ny] = target # Restore taken piece + self.board.grid[xx][yy] = choice[0] # Restore moved piece + + + + opts.sort(key = lambda e : e[1][1], reverse = True) + #sys.stderr.write(sys.argv[0] + " : After recurse, options are " + str(opts) + "\n") + + self.depth -= 1 + return list(opts[0]) + + + + # Returns [x,y] of selected piece + def select(self): + #sys.stderr.write("Getting choice...") + self.choice = self.select_best(self.colour)[0] + + #sys.stderr.write(" Done " + str(self.choice)+"\n") + return [self.choice.x, self.choice.y] + + # Returns [x,y] of square to move selected piece into + def get_move(self): + #sys.stderr.write("Choice is " + str(self.choice) + "\n") + self.choice.selected_moves = self.choice.last_moves + moves = self.prioritise_moves(self.choice) + if len(moves) > 0: + return moves[0][0] + else: + return AgentRandom.get_move(self) + +# --- agent_bishop.py --- # +import multiprocessing + +# Hacky alternative to using select for timing out players + +# WARNING: Do not wrap around HumanPlayer or things breakify +# WARNING: Do not use in general or things breakify + +class Sleeper(multiprocessing.Process): + def __init__(self, timeout): + multiprocessing.Process.__init__(self) + self.timeout = timeout + + def run(self): + time.sleep(self.timeout) + + +class Worker(multiprocessing.Process): + def __init__(self, function, args, q): + multiprocessing.Process.__init__(self) + self.function = function + self.args = args + self.q = q + + def run(self): + #print str(self) + " runs " + str(self.function) + " with args " + str(self.args) + self.q.put(self.function(*self.args)) + + + +def TimeoutFunction(function, args, timeout): + q = multiprocessing.Queue() + w = Worker(function, args, q) + s = Sleeper(timeout) + w.start() + s.start() + while True: # Busy loop of crappyness + if not w.is_alive(): + s.terminate() + result = q.get() + w.join() + #print "TimeoutFunction gets " + str(result) + return result + elif not s.is_alive(): + w.terminate() + s.join() + raise Exception("TIMEOUT") + + + + +# A player that wraps another player and times out its moves +# Uses threads +# A (crappy) alternative to the use of select() +class TimeoutPlayer(Player): + def __init__(self, base_player, timeout): + Player.__init__(self, base_player.name, base_player.colour) + self.base_player = base_player + self.timeout = timeout + + def select(self): + return TimeoutFunction(self.base_player.select, [], self.timeout) + + + def get_move(self): + return TimeoutFunction(self.base_player.get_move, [], self.timeout) + + def update(self, result): + return TimeoutFunction(self.base_player.update, [result], self.timeout) + + def quit(self, final_result): + return TimeoutFunction(self.base_player.quit, [final_result], self.timeout) +# --- timeout_player.py --- # +import socket +import select + +network_timeout_start = -1.0 # Timeout in seconds to wait for the start of a message +network_timeout_delay = 1.0 # Maximum time between two characters being received + +class NetworkPlayer(Player): + def __init__(self, colour, network, player): + Player.__init__(self, "@network:"+str(network.address), colour) + self.player = player + self.network = network + + def __str__(self): + return "NetworkPlayer<"+str(self.colour)+","+str(self.player)+">" + + def select(self): + #debug(str(self) + " select called") + if self.player != None: + s = self.player.select() + self.send_message(str(s[0]) + " " + str(s[1])) + else: + s = map(int, self.get_response().split(" ")) + for p in game.players: + if p != self and isinstance(p, NetworkPlayer) and p.player == None: + p.network.send_message(str(s[0]) + " " + str(s[1])) + if s == [-1,-1]: + game.final_result = "network terminate" + game.stop() + return s + + def send_message(self, message): + #debug(str(self) + " send_message(\""+str(message)+"\") called") + self.network.send_message(message) + + def get_response(self): + #debug(str(self) + " get_response() called") + s = self.network.get_response() + #debug(str(self) + " get_response() returns \""+str(s)+"\"") + return s + + + def get_move(self): + #debug(str(self) + " get_move called") + if self.player != None: + s = self.player.get_move() + self.send_message(str(s[0]) + " " + str(s[1])) + else: + s = map(int, self.get_response().split(" ")) + for p in game.players: + if p != self and isinstance(p, NetworkPlayer) and p.player == None: + p.network.send_message(str(s[0]) + " " + str(s[1])) + + if s == [-1,-1]: + game.final_result = "network terminate" + game.stop() + return s + + def update(self, result): + #debug(str(self) + " update(\""+str(result)+"\") called") + if self.network.server == True: + if self.player == None: + self.send_message(result) + elif self.player != None: + result = self.get_response() + if result == "-1 -1": + game.final_result = "network terminate" + game.stop() + return "-1 -1" + self.board.update(result, deselect=False) + + + + if self.player != None: + result = self.player.update(result) + + return result + + + + def base_player(self): + if self.player == None: + return self + else: + return self.player.base_player() + + def quit(self, result): + try: + self.send_message("-1 -1") + except: + pass + +class Network(): + def __init__(self, address = (None,4562)): + self.socket = socket.socket() + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + #self.socket.setblocking(0) + self.address = address + self.server = (address[0] == None) + + + self.connected = False + + def connect(self): + #debug(str(self) + "Tries to connect") + self.connected = True + if self.address[0] == None: + self.host = "0.0.0.0" #socket.gethostname() # Breaks things??? + self.socket.bind((self.host, self.address[1])) + self.socket.listen(5) + + self.src, self.actual_address = self.socket.accept() + + self.src.send("ok\n") + s = self.get_response() + if s == "QUIT": + self.src.close() + return + elif s != "ok": + self.src.close() + self.__init__(colour, (self.address[0], int(s)), baseplayer) + return + + else: + time.sleep(0.3) + self.socket.connect(self.address) + self.src = self.socket + self.src.send("ok\n") + s = self.get_response() + if s == "QUIT": + self.src.close() + return + elif s != "ok": + self.src.close() + self.__init__(colour, (self.address[0], int(s)), baseplayer) + return + + + + def __str__(self): + return "@network:"+str(self.address) + + def get_response(self): + + # Timeout the start of the message (first character) + if network_timeout_start > 0.0: + ready = select.select([self.src], [], [], network_timeout_start)[0] + else: + ready = [self.src] + if self.src in ready: + s = self.src.recv(1) + else: + raise Exception("UNRESPONSIVE") + + + while s[len(s)-1] != '\n': + # Timeout on each character in the message + if network_timeout_delay > 0.0: + ready = select.select([self.src], [], [], network_timeout_delay)[0] + else: + ready = [self.src] + if self.src in ready: + s += self.src.recv(1) + else: + raise Exception("UNRESPONSIVE") + + + return s.strip(" \r\n") + + def send_message(self,s): + if network_timeout_start > 0.0: + ready = select.select([], [self.src], [], network_timeout_start)[1] + else: + ready = [self.src] + + if self.src in ready: + self.src.send(s + "\n") + else: + raise Exception("UNRESPONSIVE") + + + + def close(self): + self.src.shutdown() + self.src.close() +# --- network.py --- # +import threading + +# A thread that can be stopped! +# Except it can only be stopped if it checks self.stopped() periodically +# So it can sort of be stopped +class StoppableThread(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + self._stop = threading.Event() + + def stop(self): + self._stop.set() + + def stopped(self): + return self._stop.isSet() +# --- thread_util.py --- # +log_files = [] +import datetime +import urllib2 + +class LogFile(): + def __init__(self, log, name): + self.name = name + self.log = log + self.logged = [] + self.log.write("# Log starts " + str(datetime.datetime.now()) + "\n") + + def write(self, s): + now = datetime.datetime.now() + self.log.write(str(now) + " : " + s + "\n") + self.logged.append((now, s)) + + def setup(self, board, players): + + for p in players: + self.log.write("# " + str(p.colour) + " : " + str(p.name) + "\n") + + self.log.write("# Initial board\n") + for x in range(0, w): + for y in range(0, h): + if board.grid[x][y] != None: + self.log.write(str(board.grid[x][y]) + "\n") + + self.log.write("# Start game\n") + + def close(self): + self.log.write("# EOF\n") + if self.log != sys.stdout: + self.log.close() + +class ShortLog(LogFile): + def __init__(self, file_name): + if file_name == "": + self.log = sys.stdout + else: + self.log = open(file_name, "w", 0) + LogFile.__init__(self, self.log, "@"+file_name) + self.file_name = file_name + self.phase = 0 + + def write(self, s): + now = datetime.datetime.now() + self.logged.append((now, s)) + + if self.phase == 0: + if self.log != sys.stdout: + self.log.close() + self.log = open(self.file_name, "w", 0) + self.log.write("# Short log updated " + str(datetime.datetime.now()) + "\n") + LogFile.setup(self, game.board, game.players) + + elif self.phase == 1: + for message in self.logged[len(self.logged)-2:]: + self.log.write(str(message[0]) + " : " + message[1] + "\n") + + self.phase = (self.phase + 1) % 2 + + def close(self): + if self.phase == 1: + ending = self.logged[len(self.logged)-1] + self.log.write(str(ending[0]) + " : " + ending[1] + "\n") + self.log.write("# EOF\n") + if self.log != sys.stdout: + self.log.close() + + +class HeadRequest(urllib2.Request): + def get_method(self): + return "HEAD" + +class HttpGetter(StoppableThread): + def __init__(self, address): + StoppableThread.__init__(self) + self.address = address + self.log = urllib2.urlopen(address) + self.lines = [] + self.lock = threading.RLock() #lock for access of self.state + self.cond = threading.Condition() # conditional + + def run(self): + while not self.stopped(): + line = self.log.readline() + if line == "": + date_mod = datetime.datetime.strptime(self.log.headers['last-modified'], "%a, %d %b %Y %H:%M:%S GMT") + self.log.close() + + next_log = urllib2.urlopen(HeadRequest(self.address)) + date_new = datetime.datetime.strptime(next_log.headers['last-modified'], "%a, %d %b %Y %H:%M:%S GMT") + while date_new <= date_mod and not self.stopped(): + next_log = urllib2.urlopen(HeadRequest(self.address)) + date_new = datetime.datetime.strptime(next_log.headers['last-modified'], "%a, %d %b %Y %H:%M:%S GMT") + if self.stopped(): + break + + self.log = urllib2.urlopen(self.address) + line = self.log.readline() + + self.cond.acquire() + self.lines.append(line) + self.cond.notifyAll() + self.cond.release() + + #sys.stderr.write(" HttpGetter got \'" + str(line) + "\'\n") + + self.log.close() + + + + + +class HttpReplay(): + def __init__(self, address): + self.getter = HttpGetter(address) + self.getter.start() + + def readline(self): + self.getter.cond.acquire() + while len(self.getter.lines) == 0: + self.getter.cond.wait() + + result = self.getter.lines[0] + self.getter.lines = self.getter.lines[1:] + self.getter.cond.release() + + return result + + + def close(self): + self.getter.stop() + +class FileReplay(): + def __init__(self, filename): + self.f = open(filename, "r", 0) + self.filename = filename + self.mod = os.path.getmtime(filename) + self.count = 0 + + def readline(self): + line = self.f.readline() + + while line == "": + mod2 = os.path.getmtime(self.filename) + if mod2 > self.mod: + #sys.stderr.write("File changed!\n") + self.mod = mod2 + self.f.close() + self.f = open(self.filename, "r", 0) + + new_line = self.f.readline() + + if " ".join(new_line.split(" ")[0:3]) != "# Short log": + for i in range(self.count): + new_line = self.f.readline() + #sys.stderr.write("Read back " + str(i) + ": " + str(new_line) + "\n") + new_line = self.f.readline() + else: + self.count = 0 + + line = new_line + + self.count += 1 + return line + + def close(self): + self.f.close() + + +def log(s): + for l in log_files: + l.write(s) + +def debug(s): + sys.stderr.write("# DEBUG: " + s + "\n") + + +def log_init(board, players): + for l in log_files: + l.setup(board, players) + +# --- log.py --- # + + + + + +# A thread that runs the game +class GameThread(StoppableThread): + def __init__(self, board, players, server = True): + StoppableThread.__init__(self) + self.board = board + self.players = players + self.state = {"turn" : None} # The game state + self.error = 0 # Whether the thread exits with an error + self.lock = threading.RLock() #lock for access of self.state + self.cond = threading.Condition() # conditional for some reason, I forgot + self.final_result = "" + self.server = server + + + + + + + # Run the game (run in new thread with start(), run in current thread with run()) + def run(self): + result = "" + while not self.stopped(): + + for p in self.players: + with self.lock: + self.state["turn"] = p.base_player() + #try: + if True: + [x,y] = p.select() # Player selects a square + if self.stopped(): + #debug("Quitting in select") + break + + if isinstance(p, NetworkPlayer): + if p.network.server == True: + result = self.board.select(x, y, colour = p.colour) + else: + result = None + + else: + result = self.board.select(x, y, colour = p.colour) + + result = p.update(result) + if self.stopped(): + break + for p2 in self.players: + if p2 == p: + continue + p2.update(result) # Inform players of what happened + if self.stopped(): + break + + if self.stopped(): + break + + + log(result) + + target = self.board.grid[x][y] + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = self.board.possible_moves(target) + graphics.state["select"] = target + + time.sleep(turn_delay) + + + if len(self.board.possible_moves(target)) == 0: + #print "Piece cannot move" + target.deselect() + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = None + graphics.state["select"] = None + graphics.state["dest"] = None + continue + + try: + [x2,y2] = p.get_move() # Player selects a destination + except: + self.stop() + + if self.stopped(): + #debug("Quitting in get_move") + break + + if isinstance(p, NetworkPlayer): + if p.network.server == True: + result = str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2) + self.board.update_move(x, y, x2, y2) + else: + result = None + + else: + result = str(x) + " " + str(y) + " -> " + str(x2) + " " + str(y2) + self.board.update_move(x, y, x2, y2) + + result = p.update(result) + if self.stopped(): + break + for p2 in self.players: + if p2 == p: + continue + p2.update(result) # Inform players of what happened + if self.stopped(): + break + + if self.stopped(): + break + + + + log(result) + + + + + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = [[x2,y2]] + + time.sleep(turn_delay) + + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["select"] = None + graphics.state["dest"] = None + graphics.state["moves"] = None + + # Commented out exception stuff for now, because it makes it impossible to tell if I made an IndentationError somewhere + # except Exception,e: + # result = e.message + # #sys.stderr.write(result + "\n") + # + # self.stop() + # with self.lock: + # self.final_result = self.state["turn"].colour + " " + e.message + + end = self.board.end_condition() + if end != None: + with self.lock: + if end == "DRAW": + self.final_result = self.state["turn"].colour + " " + end + else: + self.final_result = end + self.stop() + + if self.stopped(): + break + + + for p2 in self.players: + p2.quit(self.final_result) + + log(self.final_result) + + if isinstance(graphics, GraphicsThread): + graphics.stop() + + +# A thread that replays a log file +class ReplayThread(GameThread): + def __init__(self, players, src, end=False,max_moves=None): + self.board = Board(style="empty") + self.board.max_moves = max_moves + GameThread.__init__(self, self.board, players) + self.src = src + self.end = end + + self.reset_board(self.src.readline()) + + def reset_board(self, line): + agent_str = "" + self_str = "" + while line != "# Start game" and line != "# EOF": + + while line == "": + line = self.src.readline().strip(" \r\n") + continue + + if line[0] == '#': + line = self.src.readline().strip(" \r\n") + continue + + self_str += line + "\n" + + if self.players[0].name == "dummy" and self.players[1].name == "dummy": + line = self.src.readline().strip(" \r\n") + continue + + tokens = line.split(" ") + types = map(lambda e : e.strip("[] ,'"), tokens[2:4]) + for i in range(len(types)): + if types[i][0] == "?": + types[i] = "unknown" + + agent_str += tokens[0] + " " + tokens[1] + " " + str(types) + " ".join(tokens[4:]) + "\n" + line = self.src.readline().strip(" \r\n") + + for p in self.players: + p.reset_board(agent_str) + + + self.board.reset_board(self_str) + + + def run(self): + move_count = 0 + last_line = "" + line = self.src.readline().strip(" \r\n") + while line != "# EOF": + + + if self.stopped(): + break + + if len(line) <= 0: + continue + + + if line[0] == '#': + last_line = line + line = self.src.readline().strip(" \r\n") + continue + + tokens = line.split(" ") + if tokens[0] == "white" or tokens[0] == "black": + self.reset_board(line) + last_line = line + line = self.src.readline().strip(" \r\n") + continue + + move = line.split(":") + move = move[len(move)-1].strip(" \r\n") + tokens = move.split(" ") + + + try: + [x,y] = map(int, tokens[0:2]) + except: + last_line = line + self.stop() + break + + log(move) + + target = self.board.grid[x][y] + with self.lock: + if target.colour == "white": + self.state["turn"] = self.players[0] + else: + self.state["turn"] = self.players[1] + + move_piece = (tokens[2] == "->") + if move_piece: + [x2,y2] = map(int, tokens[len(tokens)-2:]) + + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["select"] = target + + if not move_piece: + self.board.update_select(x, y, int(tokens[2]), tokens[len(tokens)-1]) + if isinstance(graphics, GraphicsThread): + with graphics.lock: + if target.current_type != "unknown": + graphics.state["moves"] = self.board.possible_moves(target) + else: + graphics.state["moves"] = None + time.sleep(turn_delay) + else: + self.board.update_move(x, y, x2, y2) + if isinstance(graphics, GraphicsThread): + with graphics.lock: + graphics.state["moves"] = [[x2,y2]] + time.sleep(turn_delay) + with graphics.lock: + graphics.state["select"] = None + graphics.state["moves"] = None + graphics.state["dest"] = None + + + + + + for p in self.players: + p.update(move) + + last_line = line + line = self.src.readline().strip(" \r\n") + + + end = self.board.end_condition() + if end != None: + self.final_result = end + self.stop() + break + + + + + + + + + + + + + + + + if self.end and isinstance(graphics, GraphicsThread): + #graphics.stop() + pass # Let the user stop the display + elif not self.end and self.board.end_condition() == None: + global game + # Work out the last move + + t = last_line.split(" ") + if t[len(t)-2] == "black": + self.players.reverse() + elif t[len(t)-2] == "white": + pass + elif self.state["turn"] != None and self.state["turn"].colour == "white": + self.players.reverse() + + + game = GameThread(self.board, self.players) + game.run() + else: + pass + + + +def opponent(colour): + if colour == "white": + return "black" + else: + return "white" +# --- game.py --- # +try: + import pygame +except: + pass +import os + +# Dictionary that stores the unicode character representations of the different pieces +# Chess was clearly the reason why unicode was invented +# For some reason none of the pygame chess implementations I found used them! +piece_char = {"white" : {"king" : u'\u2654', + "queen" : u'\u2655', + "rook" : u'\u2656', + "bishop" : u'\u2657', + "knight" : u'\u2658', + "pawn" : u'\u2659', + "unknown" : '?'}, + "black" : {"king" : u'\u265A', + "queen" : u'\u265B', + "rook" : u'\u265C', + "bishop" : u'\u265D', + "knight" : u'\u265E', + "pawn" : u'\u265F', + "unknown" : '?'}} + +images = {"white" : {}, "black" : {}} +small_images = {"white" : {}, "black" : {}} + +def create_images(grid_sz, font_name=os.path.join(os.path.curdir, "data", "DejaVuSans.ttf")): + + # Get the font sizes + l_size = 5*(grid_sz[0] / 8) + s_size = 3*(grid_sz[0] / 8) + + for c in piece_char.keys(): + + if c == "black": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0))}) + small_images[c].update({p : pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0))}) + elif c == "white": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size+1).render(piece_char["black"][p], True,(255,255,255))}) + images[c][p].blit(pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0)),(0,0)) + small_images[c].update({p : pygame.font.Font(font_name, s_size+1).render(piece_char["black"][p],True,(255,255,255))}) + small_images[c][p].blit(pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0)),(0,0)) + + +def load_images(image_dir=os.path.join(os.path.curdir, "data", "images")): + if not os.path.exists(image_dir): + raise Exception("Couldn't load images from " + image_dir + " (path doesn't exist)") + for c in piece_char.keys(): + for p in piece_char[c].keys(): + images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + ".png"))}) + small_images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + "_small.png"))}) +# --- images.py --- # +graphics_enabled = True + +try: + import pygame + os.environ["SDL_VIDEO_ALLOW_SCREENSAVER"] = "1" +except: + graphics_enabled = False + +import time + + + +# A thread to make things pretty +class GraphicsThread(StoppableThread): + def __init__(self, board, title = "UCC::Progcomp 2013 - QChess", grid_sz = [80,80]): + StoppableThread.__init__(self) + + self.board = board + pygame.init() + self.window = pygame.display.set_mode((grid_sz[0] * w, grid_sz[1] * h)) + pygame.display.set_caption(title) + + #print "Initialised properly" + + self.grid_sz = grid_sz[:] + self.state = {"select" : None, "dest" : None, "moves" : None, "overlay" : None, "coverage" : None} + self.error = 0 + self.lock = threading.RLock() + self.cond = threading.Condition() + self.sleep_timeout = None + self.last_event = time.time() + self.blackout = False + + #print "Test font" + pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 32).render("Hello", True,(0,0,0)) + + #load_images() + create_images(grid_sz) + + """ + for c in images.keys(): + for p in images[c].keys(): + images[c][p] = images[c][p].convert(self.window) + small_images[c][p] = small_images[c][p].convert(self.window) + """ + + + + + + # On the run from the world + def run(self): + + while not self.stopped(): + + if self.sleep_timeout == None or (time.time() - self.last_event) < self.sleep_timeout: + + #print "Display grid" + self.board.display_grid(window = self.window, grid_sz = self.grid_sz) # Draw the board + + #print "Display overlay" + self.overlay() + + #print "Display pieces" + self.board.display_pieces(window = self.window, grid_sz = self.grid_sz) # Draw the board + self.blackout = False + + elif pygame.mouse.get_focused() and not self.blackout: + os.system("xset dpms force off") + self.blackout = True + self.window.fill((0,0,0)) + + pygame.display.flip() + + for event in pygame.event.get(): + self.last_event = time.time() + if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_q): + if isinstance(game, GameThread): + with game.lock: + game.final_result = "" + if game.state["turn"] != None: + game.final_result = game.state["turn"].colour + " " + game.final_result += "terminated" + game.stop() + self.stop() + break + elif event.type == pygame.MOUSEBUTTONDOWN: + self.mouse_down(event) + + elif event.type == pygame.MOUSEBUTTONUP: + self.mouse_up(event) + + + + + + + + + self.message("Game ends, result \""+str(game.final_result) + "\"") + time.sleep(1) + + # Wake up anyone who is sleeping + self.cond.acquire() + self.cond.notify() + self.cond.release() + + pygame.quit() # Time to say goodbye + + # Mouse release event handler + def mouse_up(self, event): + if event.button == 3: + with self.lock: + self.state["overlay"] = None + elif event.button == 2: + with self.lock: + self.state["coverage"] = None + + # Mouse click event handler + def mouse_down(self, event): + if event.button == 1: + m = [event.pos[i] / self.grid_sz[i] for i in range(2)] + if isinstance(game, GameThread): + with game.lock: + p = game.state["turn"] + else: + p = None + + + if isinstance(p, HumanPlayer): + with self.lock: + s = self.board.grid[m[0]][m[1]] + select = self.state["select"] + if select == None: + if s != None and s.colour != p.colour: + self.message("Wrong colour") # Look at all this user friendliness! + time.sleep(1) + return + # Notify human player of move + self.cond.acquire() + with self.lock: + self.state["select"] = s + self.state["dest"] = None + self.cond.notify() + self.cond.release() + return + + if select == None: + return + + + if self.state["moves"] == None: + return + + if not m in self.state["moves"]: + self.message("Illegal Move") # I still think last year's mouse interface was adequate + time.sleep(2) + return + + with self.lock: + if self.state["dest"] == None: + self.cond.acquire() + self.state["dest"] = m + self.state["select"] = None + self.state["moves"] = None + self.cond.notify() + self.cond.release() + elif event.button == 3: + m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))] + if isinstance(game, GameThread): + with game.lock: + p = game.state["turn"] + else: + p = None + + + if isinstance(p, HumanPlayer): + with self.lock: + self.state["overlay"] = self.board.probability_grid(self.board.grid[m[0]][m[1]]) + + elif event.button == 2: + m = [event.pos[i] / self.grid_sz[i] for i in range(len(event.pos))] + if isinstance(game, GameThread): + with game.lock: + p = game.state["turn"] + else: + p = None + + + if isinstance(p, HumanPlayer): + with self.lock: + self.state["coverage"] = self.board.coverage(m[0], m[1], None, self.state["select"]) + + # Draw the overlay + def overlay(self): + + square_img = pygame.Surface((self.grid_sz[0], self.grid_sz[1]),pygame.SRCALPHA) # A square image + # Draw square over the selected piece + with self.lock: + select = self.state["select"] + if select != None: + mp = [self.grid_sz[i] * [select.x, select.y][i] for i in range(len(self.grid_sz))] + square_img.fill(pygame.Color(0,255,0,64)) + self.window.blit(square_img, mp) + # If a piece is selected, draw all reachable squares + # (This quality user interface has been patented) + with self.lock: + m = self.state["moves"] + if m != None: + square_img.fill(pygame.Color(255,0,0,128)) # Draw them in blood red + for move in m: + mp = [self.grid_sz[i] * move[i] for i in range(2)] + self.window.blit(square_img, mp) + # If a piece is overlayed, show all squares that it has a probability to reach + with self.lock: + m = self.state["overlay"] + if m != None: + for x in range(w): + for y in range(h): + if m[x][y] > 0.0: + mp = [self.grid_sz[i] * [x,y][i] for i in range(2)] + square_img.fill(pygame.Color(255,0,255,int(m[x][y] * 128))) # Draw in purple + self.window.blit(square_img, mp) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14) + text = font.render("{0:.2f}".format(round(m[x][y],2)), 1, pygame.Color(0,0,0)) + self.window.blit(text, mp) + + # If a square is selected, highlight all pieces that have a probability to reach it + with self.lock: + m = self.state["coverage"] + if m != None: + for p in m: + mp = [self.grid_sz[i] * [p.x,p.y][i] for i in range(2)] + square_img.fill(pygame.Color(0,255,255, int(m[p] * 196))) # Draw in pale blue + self.window.blit(square_img, mp) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), 14) + text = font.render("{0:.2f}".format(round(m[p],2)), 1, pygame.Color(0,0,0)) + self.window.blit(text, mp) + # Draw a square where the mouse is + # This also serves to indicate who's turn it is + + if isinstance(game, GameThread): + with game.lock: + turn = game.state["turn"] + else: + turn = None + + if isinstance(turn, HumanPlayer): + mp = [self.grid_sz[i] * int(pygame.mouse.get_pos()[i] / self.grid_sz[i]) for i in range(2)] + square_img.fill(pygame.Color(0,0,255,128)) + if turn.colour == "white": + c = pygame.Color(255,255,255) + else: + c = pygame.Color(0,0,0) + pygame.draw.rect(square_img, c, (0,0,self.grid_sz[0], self.grid_sz[1]), self.grid_sz[0]/10) + self.window.blit(square_img, mp) + + # Message in a bottle + def message(self, string, pos = None, colour = None, font_size = 20): + #print "Drawing message..." + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size) + if colour == None: + colour = pygame.Color(0,0,0) + + text = font.render(string, 1, colour) + + + s = pygame.Surface((text.get_width(), text.get_height()), pygame.SRCALPHA) + s.fill(pygame.Color(128,128,128)) + + tmp = self.window.get_size() + + if pos == None: + pos = (tmp[0] / 2 - text.get_width() / 2, tmp[1] / 3 - text.get_height()) + else: + pos = (pos[0]*text.get_width() + tmp[0] / 2 - text.get_width() / 2, pos[1]*text.get_height() + tmp[1] / 3 - text.get_height()) + + + rect = (pos[0], pos[1], text.get_width(), text.get_height()) + + pygame.draw.rect(self.window, pygame.Color(0,0,0), pygame.Rect(rect), 1) + self.window.blit(s, pos) + self.window.blit(text, pos) + + pygame.display.flip() + + def getstr(self, prompt = None): + s = pygame.Surface((self.window.get_width(), self.window.get_height())) + s.blit(self.window, (0,0)) + result = "" + + while True: + #print "LOOP" + if prompt != None: + self.message(prompt) + self.message(result, pos = (0, 1)) + + pygame.event.pump() + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return None + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_BACKSPACE: + result = result[0:len(result)-1] + self.window.blit(s, (0,0)) # Revert the display + continue + + + try: + if event.unicode == '\r': + return result + + result += str(event.unicode) + except: + continue + + + # Function to pick a button + def SelectButton(self, choices, prompt = None, font_size=20): + + #print "Select button called!" + self.board.display_grid(self.window, self.grid_sz) + if prompt != None: + self.message(prompt) + font = pygame.font.Font(os.path.join(os.path.curdir, "data", "DejaVuSans.ttf"), font_size) + targets = [] + sz = self.window.get_size() + + + for i in range(len(choices)): + c = choices[i] + + text = font.render(c, 1, pygame.Color(0,0,0)) + p = (sz[0] / 2 - (1.5*text.get_width())/2, sz[1] / 2 +(i-1)*text.get_height()+(i*2)) + targets.append((p[0], p[1], p[0] + 1.5*text.get_width(), p[1] + text.get_height())) + + while True: + mp =pygame.mouse.get_pos() + for i in range(len(choices)): + c = choices[i] + if mp[0] > targets[i][0] and mp[0] < targets[i][2] and mp[1] > targets[i][1] and mp[1] < targets[i][3]: + font_colour = pygame.Color(255,0,0) + box_colour = pygame.Color(0,0,255,128) + else: + font_colour = pygame.Color(0,0,0) + box_colour = pygame.Color(128,128,128) + + text = font.render(c, 1, font_colour) + s = pygame.Surface((text.get_width()*1.5, text.get_height()), pygame.SRCALPHA) + s.fill(box_colour) + pygame.draw.rect(s, (0,0,0), (0,0,1.5*text.get_width(), text.get_height()), self.grid_sz[0]/10) + s.blit(text, ((text.get_width()*1.5)/2 - text.get_width()/2 ,0)) + self.window.blit(s, targets[i][0:2]) + + + pygame.display.flip() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return None + elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: + for i in range(len(targets)): + t = targets[i] + if event.pos[0] > t[0] and event.pos[0] < t[2]: + if event.pos[1] > t[1] and event.pos[1] < t[3]: + return i + #print "Reject " + str(i) + str(event.pos) + " vs " + str(t) + + + # Function to choose between dedicated server or normal play + def SelectServer(self): + + choice = self.SelectButton(["Normal", "Join Eigenserver"],prompt="Game type?") + if choice == 0: + return None + choice = self.SelectButton(["progcomp.ucc", "other"], prompt="Address?") + if choice == 0: + return "progcomp.ucc.asn.au" + else: + return self.getstr(prompt = "Enter address:") + + # Function to pick players in a nice GUI way + def SelectPlayers(self, players = []): + + + #print "SelectPlayers called" + + missing = ["white", "black"] + for p in players: + missing.remove(p.colour) + + for colour in missing: + + + choice = self.SelectButton(["human", "agent", "network"],prompt = "Choose " + str(colour) + " player") + if choice == 0: + players.append(HumanPlayer("human", colour)) + elif choice == 1: + import inspect + internal_agents = inspect.getmembers(sys.modules[__name__], inspect.isclass) + internal_agents = [x for x in internal_agents if issubclass(x[1], InternalAgent)] + internal_agents.remove(('InternalAgent', InternalAgent)) + if len(internal_agents) > 0: + choice2 = self.SelectButton(["internal", "external"], prompt="Type of agent") + else: + choice2 = 1 + + if choice2 == 0: + agent = internal_agents[self.SelectButton(map(lambda e : e[0], internal_agents), prompt="Choose internal agent")] + players.append(agent[1](agent[0], colour)) + elif choice2 == 1: + try: + import Tkinter + from tkFileDialog import askopenfilename + root = Tkinter.Tk() # Need a root to make Tkinter behave + root.withdraw() # Some sort of magic incantation + path = askopenfilename(parent=root, initialdir="../agents",title= +'Choose an agent.') + if path == "": + return self.SelectPlayers() + players.append(make_player(path, colour)) + except: + + p = None + while p == None: + self.board.display_grid(self.window, self.grid_sz) + pygame.display.flip() + path = self.getstr(prompt = "Enter path:") + if path == None: + return None + + if path == "": + return self.SelectPlayers() + + try: + p = make_player(path, colour) + except: + self.board.display_grid(self.window, self.grid_sz) + pygame.display.flip() + self.message("Invalid path!") + time.sleep(1) + p = None + players.append(p) + elif choice == 1: + address = "" + while address == "": + self.board.display_grid(self.window, self.grid_sz) + + address = self.getstr(prompt = "Address? (leave blank for server)") + if address == None: + return None + if address == "": + address = None + continue + try: + map(int, address.split(".")) + except: + self.board.display_grid(self.window, self.grid_sz) + self.message("Invalid IPv4 address!") + address = "" + + players.append(NetworkReceiver(colour, address)) + else: + return None + #print str(self) + ".SelectPlayers returns " + str(players) + return players + + + +# --- graphics.py --- # +def dedicated_server(): + global log_files + + max_games = 5 + games = [] + gameID = 0 + while True: + # Get players + gameID += 1 + log("Getting clients...") + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(("0.0.0.0", 4562)) + s.listen(2) + ss = s.accept() + + log("Got white player") + + args = ["python", "qchess.py", "--no-graphics", "@network::"+str(4600+2*len(games)), "@network::"+str(4600+2*len(games))] + if len(log_files) != 0: + for l in log_files: + if l.name == "": + args.append("--log") + else: + args.append("--log="+str(l.name)+"_"+str(gameID)) + + g = subprocess.Popen(args, stdout=subprocess.PIPE) + games.append(g) + + time.sleep(0.5) + ss[0].send("white " + str(4600 + 2*(len(games)-1))) + ss[0].shutdown(socket.SHUT_RD) + ss[0].close() + + time.sleep(0.5) + ss = s.accept() + + log("Got black player") + + time.sleep(0.5) + ss[0].send("black " + str(4600 + 2*(len(games)-1))) + ss[0].shutdown(socket.SHUT_RD) + ss[0].close() + + s.shutdown(socket.SHUT_RDWR) + s.close() + + + while len(games) > max_games: + #log("Too many games; waiting for game to finish...") + ready = select.select(map(lambda e : e.stdout, games),[], []) + for r in ready[0]: + s = r.readline().strip(" \r\n").split(" ") + if s[0] == "white" or s[0] == "black": + for g in games[:]: + if g.stdout == r: + log("Game " + str(g) + " has finished") + games.remove(g) + + return 0 + +def client(addr): + + + + s = socket.socket() + s.connect((addr, 4562)) + + [colour,port] = s.recv(1024).strip(" \r\n").split(" ") + + #debug("Colour: " + colour + ", port: " + port) + + s.shutdown(socket.SHUT_RDWR) + s.close() + + if colour == "white": + p = subprocess.Popen(["python", "qchess.py", "@human", "@network:"+addr+":"+port]) + else: + p = subprocess.Popen(["python", "qchess.py", "@network:"+addr+":"+port, "@human"]) + p.wait() + return 0# --- server.py --- # +#!/usr/bin/python -u + +# Do you know what the -u does? It unbuffers stdin and stdout +# I can't remember why, but last year things broke without that + +""" + UCC::Progcomp 2013 Quantum Chess game + @author Sam Moore [SZM] "matches" + @copyright The University Computer Club, Incorporated + (ie: You can copy it for not for profit purposes) +""" + +# system python modules or whatever they are called +import sys +import os +import time + +turn_delay = 0.5 +sleep_timeout = None +[game, graphics] = [None, None] + +def make_player(name, colour): + if name[0] == '@': + if name[1:] == "human": + return HumanPlayer(name, colour) + s = name[1:].split(":") + if s[0] == "network": + ip = None + port = 4562 + #print str(s) + if len(s) > 1: + if s[1] != "": + ip = s[1] + if len(s) > 2: + port = int(s[2]) + + if ip == None: + if colour == "black": + port += 1 + elif colour == "white": + port += 1 + + return NetworkPlayer(colour, Network((ip, port)), None) + if s[0] == "internal": + + import inspect + internal_agents = inspect.getmembers(sys.modules[__name__], inspect.isclass) + internal_agents = [x for x in internal_agents if issubclass(x[1], InternalAgent)] + internal_agents.remove(('InternalAgent', InternalAgent)) + + if len(s) != 2: + sys.stderr.write(sys.argv[0] + " : '@internal' should be followed by ':' and an agent name\n") + sys.stderr.write(sys.argv[0] + " : Choices are: " + str(map(lambda e : e[0], internal_agents)) + "\n") + return None + + for a in internal_agents: + if s[1] == a[0]: + return a[1](name, colour) + + sys.stderr.write(sys.argv[0] + " : Can't find an internal agent matching \"" + s[1] + "\"\n") + sys.stderr.write(sys.argv[0] + " : Choices are: " + str(map(lambda e : e[0], internal_agents)) + "\n") + return None + + + else: + return ExternalAgent(name, colour) + + + +# The main function! It does the main stuff! +def main(argv): + + # Apparently python will silently treat things as local unless you do this + # Anyone who says "You should never use a global variable" can die in a fire + global game + global graphics + + global turn_delay + global agent_timeout + global log_files + global src_file + global graphics_enabled + global always_reveal_states + global sleep_timeout + + + server_addr = None + + max_moves = None + src_file = None + + style = "quantum" + colour = "white" + + # Get the important warnings out of the way + if platform.system() == "Windows": + sys.stderr.write(sys.argv[0] + " : Warning - You are using " + platform.system() + "\n") + if platform.release() == "Vista": + sys.stderr.write(sys.argv[0] + " : God help you.\n") + + + players = [] + i = 0 + while i < len(argv)-1: + i += 1 + arg = argv[i] + if arg[0] != '-': + p = make_player(arg, colour) + if not isinstance(p, Player): + sys.stderr.write(sys.argv[0] + " : Fatal error creating " + colour + " player\n") + return 100 + players.append(p) + if colour == "white": + colour = "black" + elif colour == "black": + pass + else: + sys.stderr.write(sys.argv[0] + " : Too many players (max 2)\n") + continue + + # Option parsing goes here + if arg[1] == '-' and arg[2:] == "classical": + style = "classical" + elif arg[1] == '-' and arg[2:] == "quantum": + style = "quantum" + elif arg[1] == '-' and arg[2:] == "reveal": + always_reveal_states = True + elif (arg[1] == '-' and arg[2:] == "graphics"): + graphics_enabled = True + elif (arg[1] == '-' and arg[2:] == "no-graphics"): + graphics_enabled = False + elif (arg[1] == '-' and arg[2:].split("=")[0] == "file"): + # Load game from file + if len(arg[2:].split("=")) == 1: + src_file = sys.stdin + else: + f = arg[2:].split("=")[1] + if f[0:7] == "http://": + src_file = HttpReplay(f) + else: + src_file = FileReplay(f.split(":")[0]) + + if len(f.split(":")) == 2: + max_moves = int(f.split(":")[1]) + + elif (arg[1] == '-' and arg[2:].split("=")[0] == "server"): + #debug("Server: " + str(arg[2:])) + if len(arg[2:].split("=")) <= 1: + server_addr = True + else: + server_addr = arg[2:].split("=")[1] + + elif (arg[1] == '-' and arg[2:].split("=")[0] == "log"): + # Log file + if len(arg[2:].split("=")) == 1: + log_files.append(LogFile(sys.stdout,"")) + else: + f = arg[2:].split("=")[1] + if f == "": + log_files.append(LogFile(sys.stdout, "")) + elif f[0] == '@': + log_files.append(ShortLog(f[1:])) + else: + log_files.append(LogFile(open(f, "w", 0), f)) + elif (arg[1] == '-' and arg[2:].split("=")[0] == "delay"): + # Delay + if len(arg[2:].split("=")) == 1: + turn_delay = 0 + else: + turn_delay = float(arg[2:].split("=")[1]) + + elif (arg[1] == '-' and arg[2:].split("=")[0] == "timeout"): + # Timeout + if len(arg[2:].split("=")) == 1: + agent_timeout = -1 + else: + agent_timeout = float(arg[2:].split("=")[1]) + elif (arg[1] == '-' and arg[2:].split("=")[0] == "blackout"): + # Screen saver delay + if len(arg[2:].split("=")) == 1: + sleep_timeout = -1 + else: + sleep_timeout = float(arg[2:].split("=")[1]) + + elif (arg[1] == '-' and arg[2:] == "help"): + # Help + os.system("less data/help.txt") # The best help function + return 0 + + # Dedicated server? + + #debug("server_addr = " + str(server_addr)) + + if server_addr != None: + if server_addr == True: + return dedicated_server() + else: + return client(server_addr) + + + # Create the board + + # Construct a GameThread! Make it global! Damn the consequences! + + if src_file != None: + # Hack to stop ReplayThread from exiting + #if len(players) == 0: + # players = [HumanPlayer("dummy", "white"), HumanPlayer("dummy", "black")] + + # Normally the ReplayThread exits if there are no players + # TODO: Decide which behaviour to use, and fix it + end = (len(players) == 0) + if end: + players = [Player("dummy", "white"), Player("dummy", "black")] + elif len(players) != 2: + sys.stderr.write(sys.argv[0] + " : Usage " + sys.argv[0] + " white black\n") + if graphics_enabled: + sys.stderr.write(sys.argv[0] + " : (You won't get a GUI, because --file was used, and the author is lazy)\n") + return 44 + game = ReplayThread(players, src_file, end=end, max_moves=max_moves) + else: + board = Board(style) + board.max_moves = max_moves + game = GameThread(board, players) + + + + + # Initialise GUI + if graphics_enabled == True: + try: + graphics = GraphicsThread(game.board, grid_sz = [64,64]) # Construct a GraphicsThread! + + graphics.sleep_timeout = sleep_timeout + + except Exception,e: + graphics = None + sys.stderr.write(sys.argv[0] + " : Got exception trying to initialise graphics\n"+str(e.message)+"\nDisabled graphics\n") + graphics_enabled = False + + # If there are no players listed, display a nice pretty menu + if len(players) != 2: + if graphics != None: + + server_addr = graphics.SelectServer() + if server_addr != None: + if server_addr == True: + return dedicated_server() + else: + return client(server_addr) + + players = graphics.SelectPlayers(players) + else: + sys.stderr.write(sys.argv[0] + " : Usage " + sys.argv[0] + " white black\n") + return 44 + + # If there are still no players, quit + if players == None or len(players) != 2: + sys.stderr.write(sys.argv[0] + " : Graphics window closed before players chosen\n") + return 45 + + old = players[:] + for p in old: + if isinstance(p, NetworkPlayer): + for i in range(len(old)): + if old[i] == p or isinstance(old[i], NetworkPlayer): + continue + players[i] = NetworkPlayer(old[i].colour, p.network, old[i]) + + for p in players: + #debug(str(p)) + if isinstance(p, NetworkPlayer): + p.board = game.board + if not p.network.connected: + if not p.network.server: + time.sleep(0.2) + p.network.connect() + + + # If using windows, select won't work; use horrible TimeoutPlayer hack + if agent_timeout > 0: + if platform.system() == "Windows": + for i in range(len(players)): + if isinstance(players[i], ExternalAgent) or isinstance(players[i], InternalAgent): + players[i] = TimeoutPlayer(players[i], agent_timeout) + + else: + warned = False + # InternalAgents get wrapped to an ExternalAgent when there is a timeout + # This is not confusing at all. + for i in range(len(players)): + if isinstance(players[i], InternalAgent): + players[i] = ExternalWrapper(players[i]) + + + + + + + + log_init(game.board, players) + + + if graphics != None: + game.start() # This runs in a new thread + graphics.run() + if game.is_alive(): + game.join() + + + error = game.error + graphics.error + else: + game.run() + error = game.error + + + for l in log_files: + l.close() + + if src_file != None and src_file != sys.stdin: + src_file.close() + + sys.stdout.write(game.final_result + "\n") + + return error + + + + + + + + +# This is how python does a main() function... +if __name__ == "__main__": + retcode = 0 + try: + retcode = main(sys.argv) + except KeyboardInterrupt: + sys.stderr.write(sys.argv[0] + " : Got KeyboardInterrupt. Stopping everything\n") + if isinstance(graphics, StoppableThread): + graphics.stop() + graphics.run() # Will clean up graphics because it is stopped, not run it (a bit dodgy) + + if isinstance(game, StoppableThread): + game.stop() + if game.is_alive(): + game.join() + retcode = 102 + #except Exception, e: + # sys.stderr.write(sys.argv[0] + " : " + e.message + "\n") + # retcode = 103 + + try: + sys.stdout.close() + except: + pass + try: + sys.stderr.close() + except: + pass + sys.exit(retcode) + + +# --- main.py --- # +# EOF - created from make on Sat Apr 20 12:19:31 WST 2013 diff --git a/agents/python/sample.py b/agents/python/sample.py old mode 100755 new mode 100644 diff --git a/agents/silverfish/silver b/agents/silverfish/silver old mode 100755 new mode 100644 diff --git a/clean.sh b/clean.sh old mode 100755 new mode 100644 diff --git a/qchess/data/images/index.html b/qchess/data/images/index.html new file mode 100644 index 0000000000000000000000000000000000000000..9daeafb9864cf43055ae93beb0afd6c7d144bfa4 --- /dev/null +++ b/qchess/data/images/index.html @@ -0,0 +1 @@ +test diff --git a/qchess/qchess.py b/qchess/qchess.py old mode 100755 new mode 100644 diff --git a/qchess/tools/images.py b/qchess/tools/images.py deleted file mode 120000 index a1ec45aa605d80fd643857c0e37ae8ff18a8013b..0000000000000000000000000000000000000000 --- a/qchess/tools/images.py +++ /dev/null @@ -1 +0,0 @@ -../src/images.py \ No newline at end of file diff --git a/qchess/tools/images.py b/qchess/tools/images.py new file mode 100644 index 0000000000000000000000000000000000000000..72848683a6db192d411098609b0811639e775741 --- /dev/null +++ b/qchess/tools/images.py @@ -0,0 +1,54 @@ +try: + import pygame +except: + pass +import os + +# Dictionary that stores the unicode character representations of the different pieces +# Chess was clearly the reason why unicode was invented +# For some reason none of the pygame chess implementations I found used them! +piece_char = {"white" : {"king" : u'\u2654', + "queen" : u'\u2655', + "rook" : u'\u2656', + "bishop" : u'\u2657', + "knight" : u'\u2658', + "pawn" : u'\u2659', + "unknown" : '?'}, + "black" : {"king" : u'\u265A', + "queen" : u'\u265B', + "rook" : u'\u265C', + "bishop" : u'\u265D', + "knight" : u'\u265E', + "pawn" : u'\u265F', + "unknown" : '?'}} + +images = {"white" : {}, "black" : {}} +small_images = {"white" : {}, "black" : {}} + +def create_images(grid_sz, font_name=os.path.join(os.path.curdir, "data", "DejaVuSans.ttf")): + + # Get the font sizes + l_size = 5*(grid_sz[0] / 8) + s_size = 3*(grid_sz[0] / 8) + + for c in piece_char.keys(): + + if c == "black": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0))}) + small_images[c].update({p : pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0))}) + elif c == "white": + for p in piece_char[c].keys(): + images[c].update({p : pygame.font.Font(font_name, l_size+1).render(piece_char["black"][p], True,(255,255,255))}) + images[c][p].blit(pygame.font.Font(font_name, l_size).render(piece_char[c][p], True,(0,0,0)),(0,0)) + small_images[c].update({p : pygame.font.Font(font_name, s_size+1).render(piece_char["black"][p],True,(255,255,255))}) + small_images[c][p].blit(pygame.font.Font(font_name, s_size).render(piece_char[c][p],True,(0,0,0)),(0,0)) + + +def load_images(image_dir=os.path.join(os.path.curdir, "data", "images")): + if not os.path.exists(image_dir): + raise Exception("Couldn't load images from " + image_dir + " (path doesn't exist)") + for c in piece_char.keys(): + for p in piece_char[c].keys(): + images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + ".png"))}) + small_images[c].update({p : pygame.image.load(os.path.join(image_dir, c + "_" + p + "_small.png"))}) diff --git a/qchess_login.sh b/qchess_login.sh old mode 100755 new mode 100644 diff --git a/run.sh b/run.sh old mode 100755 new mode 100644 diff --git a/web/images b/web/images new file mode 120000 index 0000000000000000000000000000000000000000..850630b45a918f96b09a218ecab8f9032eba5583 --- /dev/null +++ b/web/images @@ -0,0 +1 @@ +../qchess/data/images/ \ No newline at end of file diff --git a/web/logs/log b/web/logs/log new file mode 100644 index 0000000000000000000000000000000000000000..ac55277b3c6ba7737dabd2cb7ab146ebb6badc31 --- /dev/null +++ b/web/logs/log @@ -0,0 +1,14 @@ +# Log starts 2013-05-02 12:40:48.956309 +2013-05-02 12:40:48.956389 : Getting clients... +2013-05-10 09:50:45.266308 : Got white player +2013-05-10 09:50:51.328055 : Got black player +2013-05-10 09:50:51.828767 : Getting clients... +2013-05-18 17:50:16.077574 : Got white player +2013-05-18 17:50:41.904604 : Got black player +2013-05-18 17:50:42.405349 : Getting clients... +2013-05-18 18:25:12.576240 : Got white player +2013-05-18 18:25:21.680798 : Got black player +2013-05-18 18:25:22.181525 : Getting clients... +2013-05-18 18:28:48.112894 : Got white player +2013-05-18 18:28:49.116687 : Got black player +2013-05-18 18:28:49.617370 : Getting clients... diff --git a/web/logs/log_1 b/web/logs/log_1 new file mode 100644 index 0000000000000000000000000000000000000000..50f55de10e9590951b7d5ef511981d3f2a891aa2 --- /dev/null +++ b/web/logs/log_1 @@ -0,0 +1,53 @@ +# Log starts 2013-05-10 09:50:45.382246 +# white : @network:(None, 4600) +# black : @network:(None, 4601) +# Initial board +black unknown ['rook', '?queen'] at 0,0 +black unknown ['pawn', '?pawn'] at 0,1 +white unknown ['pawn', '?knight'] at 0,6 +white unknown ['rook', '?pawn'] at 0,7 +black unknown ['knight', '?bishop'] at 1,0 +black unknown ['pawn', '?pawn'] at 1,1 +white unknown ['pawn', '?knight'] at 1,6 +white unknown ['knight', '?rook'] at 1,7 +black unknown ['bishop', '?bishop'] at 2,0 +black unknown ['pawn', '?pawn'] at 2,1 +white unknown ['pawn', '?pawn'] at 2,6 +white unknown ['bishop', '?pawn'] at 2,7 +black king ['king', 'king'] at 3,0 +black unknown ['pawn', '?pawn'] at 3,1 +white unknown ['pawn', '?pawn'] at 3,6 +white king ['king', 'king'] at 3,7 +black unknown ['queen', '?rook'] at 4,0 +black unknown ['pawn', '?knight'] at 4,1 +white unknown ['pawn', '?pawn'] at 4,6 +white unknown ['queen', '?queen'] at 4,7 +black unknown ['bishop', '?rook'] at 5,0 +black unknown ['pawn', '?pawn'] at 5,1 +white unknown ['pawn', '?pawn'] at 5,6 +white unknown ['bishop', '?rook'] at 5,7 +black unknown ['knight', '?knight'] at 6,0 +black unknown ['pawn', '?pawn'] at 6,1 +white unknown ['pawn', '?pawn'] at 6,6 +white unknown ['knight', '?bishop'] at 6,7 +black unknown ['rook', '?pawn'] at 7,0 +black unknown ['pawn', '?pawn'] at 7,1 +white unknown ['pawn', '?pawn'] at 7,6 +white unknown ['rook', '?bishop'] at 7,7 +# Start game +2013-05-10 09:50:54.310819 : 4 6 1 pawn +2013-05-10 09:50:55.481921 : 4 6 -> 4 4 +2013-05-10 09:50:57.309623 : 3 1 1 pawn +2013-05-10 09:50:58.103761 : 3 1 -> 3 3 +2013-05-10 09:51:33.678172 : 5 6 0 pawn +2013-05-10 09:51:34.228357 : 5 6 -> 5 5 +2013-05-10 09:51:37.401411 : 4 1 1 knight +2013-05-10 09:51:38.490964 : 4 1 -> 5 3 +2013-05-10 09:51:40.679271 : 4 7 0 queen +2013-05-10 09:51:41.963620 : 4 7 -> 7 4 +2013-05-10 09:51:44.292005 : 3 0 0 king +2013-05-10 09:51:44.975591 : 3 0 -> 4 1 +2013-05-10 09:51:47.471950 : 7 4 0 queen +2013-05-10 09:51:51.777611 : 7 4 -> 4 1 +2013-05-10 09:51:52.278222 : white +# EOF diff --git a/web/logs/log_2 b/web/logs/log_2 new file mode 100644 index 0000000000000000000000000000000000000000..efcc3c3cff7fba3d1250a05e794099cbd464e1e0 --- /dev/null +++ b/web/logs/log_2 @@ -0,0 +1,49 @@ +# Log starts 2013-05-18 17:50:16.195914 +# white : @network:(None, 4602) +# black : @network:(None, 4603) +# Initial board +black unknown ['rook', '?queen'] at 0,0 +black unknown ['pawn', '?pawn'] at 0,1 +white unknown ['pawn', '?pawn'] at 0,6 +white unknown ['rook', '?rook'] at 0,7 +black unknown ['knight', '?pawn'] at 1,0 +black unknown ['pawn', '?knight'] at 1,1 +white unknown ['pawn', '?knight'] at 1,6 +white unknown ['knight', '?knight'] at 1,7 +black unknown ['bishop', '?rook'] at 2,0 +black unknown ['pawn', '?bishop'] at 2,1 +white unknown ['pawn', '?pawn'] at 2,6 +white unknown ['bishop', '?pawn'] at 2,7 +black king ['king', 'king'] at 3,0 +black unknown ['pawn', '?bishop'] at 3,1 +white unknown ['pawn', '?pawn'] at 3,6 +white king ['king', 'king'] at 3,7 +black unknown ['queen', '?pawn'] at 4,0 +black unknown ['pawn', '?pawn'] at 4,1 +white unknown ['pawn', '?pawn'] at 4,6 +white unknown ['queen', '?bishop'] at 4,7 +black unknown ['bishop', '?rook'] at 5,0 +black unknown ['pawn', '?pawn'] at 5,1 +white unknown ['pawn', '?pawn'] at 5,6 +white unknown ['bishop', '?queen'] at 5,7 +black unknown ['knight', '?pawn'] at 6,0 +black unknown ['pawn', '?pawn'] at 6,1 +white unknown ['pawn', '?bishop'] at 6,6 +white unknown ['knight', '?rook'] at 6,7 +black unknown ['rook', '?knight'] at 7,0 +black unknown ['pawn', '?pawn'] at 7,1 +white unknown ['pawn', '?pawn'] at 7,6 +white unknown ['rook', '?pawn'] at 7,7 +# Start game +2013-05-18 17:50:43.367638 : 3 6 1 pawn +2013-05-18 17:50:50.415485 : 3 6 -> 3 4 +2013-05-18 17:50:51.816167 : 3 1 0 pawn +2013-05-18 17:50:52.371079 : 3 1 -> 3 3 +2013-05-18 17:50:55.174875 : 4 7 0 queen +2013-05-18 17:50:57.503496 : 4 7 -> 0 3 +2013-05-18 17:50:58.565433 : 2 1 0 pawn +2013-05-18 17:50:59.316660 : 2 1 -> 2 3 +2013-05-18 17:51:00.398013 : 0 3 0 queen +2013-05-18 17:51:01.230841 : 0 3 -> 3 0 +2013-05-18 17:51:01.731467 : white +# EOF diff --git a/web/logs/log_3 b/web/logs/log_3 new file mode 100644 index 0000000000000000000000000000000000000000..325ebb13c8dbee9879ed4d7312d692e526c1289e --- /dev/null +++ b/web/logs/log_3 @@ -0,0 +1 @@ +# Log starts 2013-05-18 18:25:12.694368 diff --git a/web/logs/log_4 b/web/logs/log_4 new file mode 100644 index 0000000000000000000000000000000000000000..2ba46ac82de36bb13854d24e3320009a65c3a23c --- /dev/null +++ b/web/logs/log_4 @@ -0,0 +1,47 @@ +# Log starts 2013-05-18 18:28:48.231038 +# white : @network:(None, 4606) +# black : @network:(None, 4607) +# Initial board +black unknown ['rook', '?bishop'] at 0,0 +black unknown ['pawn', '?pawn'] at 0,1 +white unknown ['pawn', '?bishop'] at 0,6 +white unknown ['rook', '?rook'] at 0,7 +black unknown ['knight', '?knight'] at 1,0 +black unknown ['pawn', '?knight'] at 1,1 +white unknown ['pawn', '?pawn'] at 1,6 +white unknown ['knight', '?rook'] at 1,7 +black unknown ['bishop', '?queen'] at 2,0 +black unknown ['pawn', '?pawn'] at 2,1 +white unknown ['pawn', '?pawn'] at 2,6 +white unknown ['bishop', '?knight'] at 2,7 +black king ['king', 'king'] at 3,0 +black unknown ['pawn', '?pawn'] at 3,1 +white unknown ['pawn', '?pawn'] at 3,6 +white king ['king', 'king'] at 3,7 +black unknown ['queen', '?rook'] at 4,0 +black unknown ['pawn', '?pawn'] at 4,1 +white unknown ['pawn', '?pawn'] at 4,6 +white unknown ['queen', '?knight'] at 4,7 +black unknown ['bishop', '?rook'] at 5,0 +black unknown ['pawn', '?pawn'] at 5,1 +white unknown ['pawn', '?pawn'] at 5,6 +white unknown ['bishop', '?pawn'] at 5,7 +black unknown ['knight', '?pawn'] at 6,0 +black unknown ['pawn', '?pawn'] at 6,1 +white unknown ['pawn', '?pawn'] at 6,6 +white unknown ['knight', '?bishop'] at 6,7 +black unknown ['rook', '?bishop'] at 7,0 +black unknown ['pawn', '?pawn'] at 7,1 +white unknown ['pawn', '?pawn'] at 7,6 +white unknown ['rook', '?queen'] at 7,7 +# Start game +2013-05-18 18:28:51.646758 : 5 6 1 pawn +2013-05-18 18:28:52.548055 : 5 6 -> 5 4 +2013-05-18 18:28:56.728446 : 0 1 1 pawn +2013-05-18 18:28:57.297094 : 0 1 -> 0 2 +2013-05-18 18:28:58.898872 : 5 4 0 pawn +2013-05-18 18:28:59.420966 : 5 4 -> 5 3 +2013-05-18 18:29:03.940562 : 7 1 0 pawn +2013-05-18 18:29:04.499325 : 7 1 -> 7 2 +2013-05-18 18:29:10.454964 : network terminate +# EOF diff --git a/web/poster b/web/poster deleted file mode 120000 index e200c803443c1079b00ff0d52952861dba75a37f..0000000000000000000000000000000000000000 --- a/web/poster +++ /dev/null @@ -1 +0,0 @@ -../poster \ No newline at end of file diff --git a/web/poster/original.pdf b/web/poster/original.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2f68aea5657f86aeea21b7a207ae90233b1db16a Binary files /dev/null and b/web/poster/original.pdf differ diff --git a/web/poster/outlines.svg b/web/poster/outlines.svg new file mode 100644 index 0000000000000000000000000000000000000000..8f9927408e210f8dff91d58189ab6ec65c5a50b9 --- /dev/null +++ b/web/poster/outlines.svg @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + version="1.1" + inkscape:version="0.48.4 r9939" + sodipodi:docname="New document 1"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="181.83741" + inkscape:cy="458.16394" + inkscape:document-units="px" + inkscape:current-layer="g3014" + showgrid="false" + inkscape:window-width="1366" + inkscape:window-height="721" + inkscape:window-x="-2" + inkscape:window-y="24" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + transform="matrix(1.25,0,0,-1.25,-2.8571443,1030.0193)" + inkscape:label="original" + id="g3012"> + <g + transform="matrix(576,0,0,823.68,-69.498491,69.498495)" + id="g3014"> + <path + style="fill:#000000" + d="m 0.33468027,0.35396112 c -0.008514,-3.4267e-4 -0.0168728,-0.004036 -0.022092,-0.0108659 l -0.001302,-0.004097 -5.2083e-4,6.07e-5 c 1.75e-5,-1.5042e-4 1.7779e-4,-3.7746e-4 3.4722e-4,-6.6773e-4 l -1.7361e-4,-5.1598e-4 5.6424e-4,-1.2141e-4 c 0.002864,-0.004175 0.0135227,-0.0158256 0.0136719,-0.0207908 7.1951e-4,-0.0151608 -0.009796,-0.0270683 -0.0203559,-0.0364523 l 0.001215,-0.00346 c -1.449e-4,-0.008836 0.0106291,-0.00708 0.0164931,-0.007558 0.0137072,-0.002823 0.027305,0.00138 0.0410157,0.002337 0.005851,0.002878 0.0148131,-0.001851 0.0196181,0.003733 l -8.6805e-4,0.004462 -0.00434,0.007254 c -0.0117922,0.00694 -0.017316,0.0225367 -0.0222222,0.0348436 8.0069e-4,1.5002e-4 0.001607,5.7879e-4 0.002387,0.001366 0.002371,0.004766 0.00876,0.005654 0.009766,0.0114729 2.0681e-4,0.0134158 -0.0152826,0.015246 -0.0247396,0.018211 -0.00276,6.3758e-4 -0.005626,9.0337e-4 -0.008464,7.8914e-4 z m 0.001042,-0.001032 c 9.8536e-4,-1.055e-5 0.001924,-7.802e-5 0.002821,-1.8211e-4 0.002967,-3.45e-4 0.005822,-0.001734 0.009245,-0.001791 0.002081,-0.001118 0.0103853,-0.003813 0.0139323,-0.006343 0.003689,-0.00277 0.005565,-0.008887 0.001302,-0.0125048 -0.006838,0.001092 -0.018579,0.001082 -0.0246528,0.002125 l 0.002474,-0.003794 0.004253,-0.004462 c 0.002782,-3.174e-5 0.005735,-0.002978 0.008637,-0.003733 0.001314,-0.006892 0.0141962,-0.0268918 0.012283,-0.0263148 6.9296e-4,-0.001182 0.001706,-0.002758 0.002691,-0.003581 0.004096,-0.003428 0.009173,-0.005027 0.0114149,-0.0104713 l -8.681e-5,-0.007011 c -0.0101952,-9.21e-4 -0.0202864,-0.002402 -0.0304688,-0.003369 -0.0115662,-0.002382 -0.0229883,-0.001076 -0.0346355,-2.7317e-4 -0.005839,1.392e-4 -0.009691,0.007611 -0.005469,0.0119889 0.0114921,0.009289 0.0208471,0.0245858 0.0162761,0.0397909 -0.002973,0.004621 -0.006877,0.0110886 -0.0114583,0.0148116 l 0.001345,0.00428 c 0.00454,0.008441 0.0131979,0.0109093 0.0200955,0.0108355 z m 0.0141927,-0.007618 -0.004297,-1.8211e-4 5.6424e-4,-5.7668e-4 0.007769,-0.002944 0.004167,-7.2843e-4 -0.00816,0.004431 -4.34e-5,0 z m 9.5486e-4,-0.004219 -0.004123,-1.2141e-4 -0.001345,-0.001427 c 6.39e-4,-0.005577 0.008405,-0.001571 0.005469,0.001548 z m -0.00204,-0.001457 c 0.001106,-3.885e-5 4.733e-4,-0.003005 -0.001085,-5.4633e-4 4.4512e-4,4.05e-4 8.2991e-4,5.553e-4 0.001085,5.4633e-4 z m -0.006727,-0.006981 c 7.55e-4,-3.2759e-4 0.001479,-6.3623e-4 0.001953,-9.1054e-4 2.5669e-4,-1.4857e-4 2.8158e-4,5.7481e-4 6.5104e-4,-4.2493e-4 0.001094,-0.002962 -0.00349,-0.002174 -0.003212,6.6774e-4 l 6.0764e-4,6.6773e-4 z m 0.005512,-8.1949e-4 0.003602,-9.105e-5 0.0114583,9.105e-5 c -0.001782,-0.001354 -0.00449,-0.002314 -0.00842,-0.002519 -0.002066,-0.001255 -0.004247,0.002034 -0.005295,1.5176e-4 l -0.002344,0.001062 9.9827e-4,0.001305 z m -8.681e-5,-0.003794 c 0.001106,-3.885e-5 5.1671e-4,-0.003005 -0.001042,-5.4633e-4 4.4512e-4,4.0501e-4 7.8651e-4,5.553e-4 0.001042,5.4633e-4 z m 0.002995,-9.105e-5 0.003429,-4.8563e-4 c 0.007508,-0.001292 -0.00119,-0.005147 9.9826e-4,-0.0017 l -0.002821,-5.1597e-4 -0.002344,0.001062 7.3785e-4,0.001639 z m -0.009245,-0.0150544 0.001389,-0.002519 c 0.002247,-0.00615 0.0100991,-0.005737 0.0133681,-0.0104106 -0.00371,-0.005995 -0.0124039,-0.006967 -0.0178819,-0.003157 l 0.00178,0.001487 c 0.003184,0.006032 -0.002918,0.00878 -0.00599,0.012262 4.22e-4,-0.002927 0.00288,-0.005076 0.004557,-0.007284 0.003772,-0.004076 -0.005127,-0.00551 -2.6042e-4,-0.008802 0.006751,-0.00184 0.0170958,-0.002403 0.0190538,0.006677 -0.004572,0.003179 -0.0115223,0.003823 -0.0137153,0.009834 l -0.0023,0.001912 z" + id="path3027" + inkscape:connector-curvature="0" /> + <path + style="fill:#000000" + d="M 393.90625 416.6875 C 392.91602 416.79627 392.82734 417.46895 392.4375 419.375 C 392.32662 419.92142 391.65705 420.1824 390.65625 420.0625 C 389.76993 419.95659 388.85351 420.014 388.625 420.1875 C 388.39778 420.3615 388.43728 422.54991 388.71875 425.03125 C 389.19323 429.21319 389.3427 429.55732 390.9375 430.0625 C 392.63206 430.59892 392.64284 430.7034 392.4375 435.4375 C 392.18622 441.09874 391.95322 441.72721 390.0625 441.46875 C 388.93498 441.31534 388.35938 441.77347 387.3125 443.6875 C 386.9112 444.42409 385.54996 444.71019 384.375 444.90625 C 384.11767 444.85905 383.72805 444.83605 383.21875 444.84375 C 380.72121 444.88145 375.71682 445.58066 373.6875 446.3125 C 372.70902 446.66396 371.27083 447.18242 370.46875 447.46875 C 369.66667 447.75498 368.04428 448.57487 366.875 449.3125 C 365.1719 450.38946 364.82996 450.89521 365.1875 451.8125 C 365.4359 452.44159 366.07012 454.64925 366.59375 456.71875 C 367.11719 458.78835 368.59268 464.00017 369.875 468.28125 C 371.15732 472.56274 372.70277 478.69596 373.28125 481.90625 C 373.86013 485.11654 374.52373 487.9127 374.78125 488.125 C 375.04261 488.3371 375.54497 489.95299 375.875 491.71875 C 376.4654 494.88213 376.45642 494.9385 375.0625 495.78125 C 374.28739 496.25238 373.60749 496.88971 373.5625 497.21875 C 373.5176 497.54749 373.23047 497.89778 372.9375 497.96875 C 371.91582 498.21585 368.40578 502.91291 368.21875 504.28125 C 368.07259 505.34717 367.6543 505.64262 366.4375 505.5625 C 365.57278 505.5058 364.31989 505.45597 363.65625 505.4375 C 362.99025 505.4187 362.17966 505.3881 361.84375 505.375 C 361.11755 505.3472 360.10768 512.13327 360.71875 512.9375 C 360.9503 513.24226 364.3211 513.94012 368.1875 514.46875 C 372.05721 514.99743 375.4201 515.68991 375.65625 516 C 376.18401 516.69271 371.57476 550.35991 369.625 560.09375 C 367.7242 569.58553 365.79847 576.39298 364.84375 577 C 364.40671 577.27387 363.3303 580.39604 362.4375 583.96875 C 361.54254 587.53992 359.84209 592.6046 358.65625 595.21875 C 357.46753 597.8329 356.4468 600.41234 356.375 600.9375 C 356.303 601.46363 357.5037 603.53794 359.0625 605.53125 C 360.6213 607.52559 361.82806 609.61657 361.75 610.1875 C 361.55992 611.57643 359.22766 616.05862 358.53125 616.375 C 357.72528 616.74205 352.55877 623.83281 350.53125 627.34375 L 348.78125 630.34375 L 350.75 633.84375 C 351.83144 635.77013 353.26172 637.62087 353.9375 637.9375 C 354.56027 638.2294 355.18155 638.71112 355.40625 639.0625 C 355.03505 639.38182 354.66372 639.68047 354.5 639.6875 C 354.36577 639.6935 354.22766 639.7159 354.09375 639.75 C 353.55879 639.88664 352.10159 639.99338 350.84375 640 C 349.58519 640.007 348.39328 640.21914 348.1875 640.46875 C 347.98619 640.71791 348.1008 643.7751 348.46875 647.25 C 348.84175 650.7249 349.10365 653.99906 349.03125 654.53125 C 348.90075 655.48578 351.00279 657.34148 351.84375 657.34375 C 352.43797 657.68611 353.19419 658.02798 353.71875 658.21875 C 356.44208 659.21025 380.15576 662.38842 383.75 662.25 C 385.56217 662.1798 388.85325 662.05939 391.03125 661.96875 C 393.20925 661.87785 396.31923 661.8951 397.96875 662 C 401.07886 662.19768 405.18186 661.9182 417.21875 660.71875 C 423.25213 660.1169 424.37179 660.16808 424.84375 660.96875 C 425.30311 661.75099 425.56573 661.67138 426.53125 660.5625 C 427.44925 659.51025 427.83589 657.98368 428.34375 653.125 C 428.92315 647.56001 428.85448 646.88426 427.75 645.875 C 425.76784 644.05879 421.9346 639.47835 422 639 C 422.0331 638.75598 422.97098 637.38512 424.0625 635.9375 C 426.12224 633.19773 427.32838 632.34153 425.6875 629.21875 C 425.50318 629.02724 423.57316 624.66072 421.375 619.53125 L 417.375 610.21875 L 418.84375 607 C 419.14866 606.33045 419.3733 605.70161 419.5625 605.125 C 419.73001 605.42936 419.89117 605.62244 419.96875 605.46875 C 420.02075 605.36423 419.9637 604.76559 419.875 604.03125 C 419.97652 603.52928 419.99527 603.10606 419.90625 602.90625 C 419.82045 602.71332 419.74785 602.6173 419.65625 602.59375 C 419.38822 600.85279 419.03532 598.90599 418.8125 598.4375 C 418.4129 597.59014 417.76654 595.10342 417.375 592.90625 C 416.98548 590.70908 416.14825 586.7563 415.53125 584.09375 C 412.59221 571.46766 412.15581 569.26046 411.75 565.53125 C 411.50598 563.33202 411.09788 561.23855 410.875 560.875 C 410.64873 560.51052 410.25824 558.68019 410 556.8125 C 409.73936 554.94584 409.22359 551.47727 408.84375 549.09375 C 408.46445 546.71043 408.03108 541.89314 407.875 538.375 C 407.71656 534.85583 407.44276 531.2654 407.25 530.40625 C 406.4999 527.04955 406.60849 516.13291 407.40625 515.71875 C 408.36795 515.21738 418.38561 515.06863 419.90625 515.53125 L 421.75 516.09375 L 422.5 512.15625 C 422.90751 509.99718 423.13253 508.01444 423 507.71875 C 422.86608 507.42269 421.459 506.91496 419.875 506.59375 C 417.86692 506.18706 416.98125 505.72785 416.96875 505.09375 C 416.95875 504.59233 416.89285 503.75632 416.84375 503.25 C 416.79475 502.74346 417.10242 502.45612 417.5 502.59375 C 418.54849 502.95617 418.57986 501.36506 417.5625 500.25 C 417.08154 499.72696 415.41623 498.52914 413.84375 497.59375 C 412.38859 496.72815 410.80905 495.97704 410.125 495.78125 C 410.70495 493.24538 410.83418 487.53103 412.8125 483.03125 C 413.6441 481.14708 414.55501 479.3561 414.84375 479.0625 C 415.12887 478.76803 415.69395 477.49648 416.09375 476.21875 C 416.49335 474.94102 417.5784 472.34056 418.5 470.46875 C 419.42448 468.59591 421.05868 464.62217 422.125 461.625 C 424.36708 455.32129 424.4111 455.22877 425.9375 455.4375 C 427.72814 455.68152 427.41977 454.8892 424.90625 452.8125 C 423.31347 451.4977 420.80044 450.33684 416.875 449.125 C 413.73724 448.15675 410.96778 447.35082 410.6875 447.3125 C 410.4024 447.2734 409.97117 446.2589 409.75 445.0625 C 409.52824 443.8661 408.96504 442.84484 408.5 442.78125 C 408.35257 442.76105 408.23437 442.7436 408.125 442.75 C 407.96629 442.759 407.85899 442.8284 407.78125 442.90625 C 406.81333 442.87055 405.21585 442.95525 404 442.90625 L 401 442.78125 L 400.6875 440.5625 C 400.52123 439.34036 400.44349 437.99072 400.5 437.5625 C 400.5267 437.3606 400.4662 437.21895 400.375 437.125 C 400.369 437.119 400.381 437.09895 400.375 437.09375 C 400.53347 436.25888 400.9251 434.25375 401.09375 433.125 C 402.29466 433.91625 404.12966 434.82063 404.5 434.65625 C 404.96335 434.4493 405.7205 427.07668 405.4375 425.53125 C 405.3655 425.10475 404.49864 424.6678 403.5 424.53125 C 401.58076 424.2687 401.53923 424.20803 401.0625 420.09375 L 400.75 417.5 L 396.9375 416.96875 C 395.42904 416.76257 394.50039 416.62224 393.90625 416.6875 z M 395.34375 418.09375 C 395.8196 418.09443 396.39985 418.16625 397.09375 418.3125 C 399.61515 418.8448 399.69471 418.90661 400.09375 421.78125 C 400.36519 423.73337 400.83669 424.80017 401.5 424.9375 C 402.04432 425.05034 402.87359 425.2034 403.34375 425.3125 C 404.35417 425.54725 404.76781 429.18638 404.03125 431.3125 C 403.59493 432.58548 403.25653 432.70808 401.78125 432.28125 C 401.01196 432.05907 400.36043 431.97843 400.125 432.0625 C 400.122 432.06302 400.09505 432.0615 400.09375 432.0625 C 400.09075 432.0645 400.09675 432.09015 400.09375 432.09375 C 400.0928 432.09475 400.0634 432.09275 400.0625 432.09375 C 400.037 432.11075 400.03455 432.13175 400.03125 432.15625 C 400.02925 432.17165 400.02625 432.20045 400.03125 432.21875 C 399.81278 432.95048 399.52519 436.928 399.59375 437.625 C 399.38262 438.1551 399.1912 438.97703 399.125 439.875 L 398.9375 442.375 L 396.09375 442.09375 C 393.39484 441.82811 393.25913 441.73741 393.4375 440.21875 C 393.54069 439.3405 393.63346 437.51671 393.65625 436.1875 C 393.67905 434.85829 393.76666 432.84271 393.84375 431.6875 C 393.96111 429.87278 393.76215 429.51972 392.34375 429.15625 C 390.58191 428.70426 389.61616 426.44638 389.6875 422.9375 C 389.7185 421.27554 389.83866 421.15105 391.3125 421.53125 C 392.65472 421.8768 392.95934 421.71565 393.125 420.5625 C 393.37772 418.79429 393.91619 418.09172 395.34375 418.09375 z M 390.0625 442.4375 C 390.61553 442.3627 394.2743 442.79242 398.1875 443.40625 C 403.51993 444.24283 406.46673 444.21798 407.78125 443.78125 C 407.9242 444.2671 408.20459 444.90309 408.6875 445.875 C 409.87255 448.26264 411.25436 448.99615 418.625 451.15625 C 422.5094 452.29499 424.46135 453.8036 423.59375 455 C 423.21647 455.52921 422.39409 457.58773 421.75 459.5625 C 421.102 461.53696 418.12236 468.48881 415.125 475 C 409.28085 487.70547 406.9868 495.27064 407.75 495.375 C 407.8187 495.384 408.35359 495.92596 408.875 496.3125 C 408.88 496.3155 408.87 496.34045 408.875 496.34375 C 409.12471 496.98042 410.12583 497.98256 411.6875 499.03125 C 414.61106 500.99573 414.65008 501.09083 414.59375 504.21875 C 414.52535 508.01781 414.97607 508.81795 416.84375 508.125 C 417.60191 507.84416 418.89795 507.78398 419.71875 508 C 421.07536 508.35521 421.19142 508.68357 420.9375 511.28125 L 420.65625 514.125 L 418.53125 513.46875 C 417.20717 513.05459 415.58315 513.01336 414.21875 513.375 C 413.01275 513.6944 410.93655 513.98879 409.59375 514.03125 C 406.11453 514.13936 405.61799 515.21756 405.5 522.9375 C 405.4497 526.44123 405.5894 530.95382 405.8125 532.96875 C 406.03106 534.98471 406.22934 537.84259 406.25 539.34375 C 406.2925 542.59502 408.7144 558.50621 409.3125 559.5 C 409.54362 559.88301 410.26939 563.05156 410.9375 566.5625 C 411.60566 570.07344 412.30032 573.12197 412.46875 573.34375 C 412.63722 573.56614 412.69665 574.37241 412.59375 575.125 C 412.49079 575.87764 413.14018 579.67885 414.0625 583.5625 C 414.98482 587.44615 416.00333 592.01064 416.3125 593.71875 C 416.76142 596.20061 418.03638 600.97786 418.96875 603.625 C 418.94975 603.6762 418.92585 603.6949 418.90625 603.75 C 418.57217 604.70135 417.54588 606.5602 416.625 607.875 L 414.96875 610.28125 L 416.65625 613.03125 C 417.59009 614.54991 418.88724 617.24144 419.5625 619 C 420.7757 622.13585 422.29232 625.61106 424.25 629.84375 C 425.32784 632.1655 425.38195 631.96428 421.46875 637.09375 L 420.15625 638.84375 L 421.9375 641.71875 C 422.91886 643.31154 424.62853 645.34751 425.78125 646.21875 C 427.81624 647.758 427.8804 647.87953 427.34375 650.84375 C 427.04021 652.51891 426.60222 655.02865 426.375 656.40625 C 426.03012 658.50306 425.74431 658.9275 424.59375 659 C 423.83804 659.0474 420.17459 659.3403 416.46875 659.65625 C 405.78662 660.56691 400.81256 660.84327 397.25 660.75 C 395.42458 660.702 391.0653 660.6952 387.5625 660.75 C 380.68557 660.85708 356.33204 657.96523 353.375 656.6875 C 352.5671 656.33938 351.75395 656.19053 351.3125 656.28125 C 351.08541 656.0267 350.86805 655.75951 350.78125 655.53125 C 350.33197 654.35133 349.27303 643.23697 349.5 642.125 C 349.5662 641.80097 349.9348 641.53566 350.34375 641.5625 C 350.75415 641.5896 352.11356 641.55445 353.375 641.46875 C 354.87089 641.36698 356.1073 640.34743 356.5625 639.40625 C 356.89923 639.25838 357.19781 638.97544 357.25 638.59375 C 357.2919 638.2861 356.98488 637.99525 356.5625 637.9375 C 355.72082 637.82321 351.91183 632.79968 351.21875 630.90625 C 350.76947 629.67279 351.21675 628.78491 354.5625 624.03125 C 362.03617 613.43028 363.43238 611.24242 363.59375 610.0625 C 363.71831 609.15336 362.74324 607.5425 360.625 605.125 C 357.61684 601.69231 357.04055 600.17483 358.34375 599.09375 C 359.16383 598.41071 362.41033 591.12501 363.15625 588.28125 C 363.49897 586.99631 364.26465 584.6694 364.84375 583.125 C 368.9147 572.26993 370.48005 565.13749 373.78125 543.09375 C 377.42517 518.77686 377.61246 515.13041 375.1875 514.15625 C 374.43992 513.85645 371.04601 513.31845 367.65625 512.96875 L 361.5 512.34375 L 361.625 509.96875 C 361.7654 507.45172 363.20459 505.98034 364.71875 506.8125 C 365.14499 507.04477 366.47934 507.06818 367.6875 506.84375 C 369.57363 506.49575 369.85613 506.21148 369.71875 504.96875 C 369.44155 502.47712 374.21199 497.50284 376.59375 497.78125 C 377.41238 497.87805 377.54914 497.47424 377.4375 495.1875 C 377.3641 493.70282 377.02563 491.45906 376.6875 490.1875 C 375.9675 487.48108 376.33452 486.8148 378.375 487.09375 C 381.5677 487.5303 386.42578 483.42255 387.8125 479.125 C 388.78522 476.10209 388.43808 474.83636 386.25 473.40625 C 383.50039 471.60822 379.90978 471.31535 376.5625 472.625 C 374.98138 473.24114 373.56876 473.70972 373.40625 473.6875 C 373.13337 473.6502 367.96436 457.90928 366.875 453.8125 C 366.61211 452.82305 366.56913 451.60507 366.8125 451.09375 C 367.5109 449.62554 374.3404 446.90219 378.25 446.53125 C 379.89315 446.37538 382.27321 446.15245 383.125 445.9375 C 384.48408 446.2498 387.87407 445.44791 388.09375 444.46875 C 388.64815 442.00801 389.17402 442.55672 390.0625 442.4375 z M 381.53125 454.5625 C 380.05854 454.606 378.39442 454.96142 376.9375 455.625 C 373.99774 456.96245 372.60463 458.20325 373 459.09375 C 373.43848 460.08143 374.42546 460.03353 374.5625 459.03125 C 374.74099 457.72469 379.92844 455.82171 382.375 456.15625 C 384.38668 456.43218 386.19792 457.90595 386.03125 459.125 C 385.99025 459.4286 386.38456 459.71419 386.875 459.78125 C 387.36349 459.84805 387.75863 459.71545 387.78125 459.46875 C 387.84105 458.76759 386.22861 456.37442 385.03125 455.40625 C 384.28947 454.80744 383.00396 454.51902 381.53125 454.5625 z M 379.40625 461.40625 C 378.25446 461.40625 378.09893 461.75834 377.84375 463.625 C 377.61407 465.30531 377.83255 466.21169 378.65625 466.9375 C 379.65561 467.81681 379.98325 467.81135 381.53125 466.90625 C 383.54293 465.7253 384.03318 463.84121 382.6875 462.46875 C 382.17774 461.94776 380.95731 461.49097 379.96875 461.4375 C 379.76025 461.4262 379.57079 461.40625 379.40625 461.40625 z M 380.21875 462.8125 C 380.90516 462.61143 382.01606 464.37832 381.6875 465.3125 C 381.28214 466.47018 379.5803 466.1967 379.34375 464.9375 C 379.26155 464.4995 379.41835 464.22584 379.6875 464.3125 C 380.85157 464.68788 381.29906 464.43242 380.5625 463.8125 C 380.12313 463.44263 379.91811 463.00839 380.09375 462.875 C 380.13535 462.8432 380.17295 462.8259 380.21875 462.8125 z M 379.75 474.15625 C 382.39329 474.18045 385.16151 474.89037 385.5625 475.96875 C 386.52874 478.57364 385.6327 480.56828 382.5 482.75 C 376.82906 486.70469 374.62515 486.19016 374.75 480.90625 C 374.7843 479.37112 374.60335 477.93279 374.34375 477.71875 C 373.38399 476.92699 374.1617 475.57608 376.0625 474.75 C 376.7533 474.44987 377.65195 474.25555 378.625 474.1875 C 378.98989 474.162 379.37239 474.15275 379.75 474.15625 z M 388.75 534.78125 C 387.49679 535.10102 385.25742 543.55593 383.625 554.59375 C 383.13178 557.92874 381.07802 561.3906 380.4375 563.15625 C 379.84824 562.64449 374.80922 566.43444 373.90625 568.09375 C 373.29425 569.21293 373.46807 571.05315 373.78125 574.34375 C 374.15133 578.27888 374.44237 578.99556 376.28125 580.75 C 380.58865 584.86669 385.0542 584.63114 390.375 580 C 393.22675 577.51763 393.33232 577.35221 393.6875 573.71875 C 393.88881 571.66161 393.7674 568.99448 393.4375 567.8125 C 392.3921 564.06579 392.25567 560.43292 393.09375 558.53125 C 393.52575 557.5521 394.09659 555.26952 394.34375 553.46875 C 394.77719 550.35359 397.93945 540.3181 399.15625 538.21875 L 399.125 538.21875 C 399.43536 537.6824 399.49063 537.43786 399.21875 537.40625 C 399.12825 537.39575 399.00222 537.4003 398.84375 537.4375 C 397.28999 537.80229 394.52909 545.57792 392.28125 555.90625 C 389.89085 566.90546 389.87251 566.75564 391.46875 567.625 C 392.98759 568.45177 393.05711 568.99086 392.09375 574.34375 C 391.64807 576.86009 391.1059 577.963 389.6875 579.25 C 387.24879 581.46839 385.55319 581.94467 381.84375 581.4375 C 378.13431 580.92991 376.56201 579.67698 375.65625 576.46875 C 373.98225 570.53352 374.89384 567.42507 379 565.03125 C 379.60979 564.6758 380.00826 564.24435 380.25 563.875 C 380.2749 564.50533 382.20019 563.68286 382.34375 563.65625 C 382.96295 563.54145 385.6915 553.23794 387.4375 542.21875 C 387.87598 539.4481 388.5413 536.91436 388.90625 536.5625 C 389.27121 536.21141 389.40325 535.57191 389.21875 535.15625 C 389.08474 534.85402 388.92903 534.73557 388.75 534.78125 z M 416.3125 609.75 C 416.63943 609.7947 416.8572 610.11054 416.8125 610.4375 C 416.7678 610.76445 416.48319 610.98221 416.15625 610.9375 C 415.82931 610.8928 415.5803 610.60819 415.625 610.28125 C 415.6697 609.95428 415.98556 609.70531 416.3125 609.75 z " + transform="matrix(0.00138889,0,0,-9.7125097e-4,0.12462536,0.91603164)" + id="path3027-8" /> + <path + style="fill:#000000" + d="M 81.65625 476.25 C 79.260806 476.25 77.62152 477.3301 77.25 479.1875 C 76.98288 480.52392 76.564361 481.0625 75.6875 481.0625 C 74.741418 481.0625 74.46875 480.66395 74.46875 479.28125 C 74.46875 475.96182 70.225732 475.32964 69.03125 478.46875 C 68.633169 479.51276 68.65327 480.24416 69.125 480.8125 C 69.67058 481.47081 69.541652 481.55628 68.5625 481.65625 C 67.89351 481.72455 66.621642 482.14283 65.875 482.40625 C 65.584898 481.83399 64.997642 481.06575 64.125 480.1875 L 64.125 480.15625 C 62.597158 478.61906 61.031172 477.94065 59.78125 478.03125 C 58.174208 478.14726 57.05674 479.56366 57.0625 482.0625 L 58.4375 483.5625 C 58.642412 483.80115 58.878938 484.05974 59.09375 484.3125 C 59.4898 484.77852 59.86551 485.22412 60.09375 485.5625 C 60.31623 485.88682 60.86149 487.19653 61.28125 488.5 C 62.064612 490.93089 65.214338 496.71255 66.0625 497.25 C 66.32314 497.41474 66.81929 499.02959 67.15625 500.84375 C 67.493217 502.65791 68.06166 504.95835 68.4375 505.9375 C 68.81406 506.91974 69.40584 510.41373 69.75 513.71875 C 70.094297 517.0248 70.52406 521.04148 70.6875 522.625 C 70.7672 523.39746 70.897265 523.98756 71.03125 524.4375 C 70.757549 525.20795 70.40245 526.60761 70.09375 526.8125 C 68.707748 527.73398 68.262192 528.46385 67.46875 531.15625 C 66.98059 532.80773 66.678814 532.98061 63.65625 533.28125 C 61.848328 533.46092 61.09535 533.66559 61.34375 533.90625 C 61.231041 534.14905 61.45559 536.25582 61.375 537.375 C 61.078353 541.4934 61.206368 541.58666 67.03125 541.78125 L 72.25 541.9375 L 72.4375 549.03125 C 72.538048 552.93446 72.24707 559.90592 71.8125 564.5 C 71.377922 569.09716 71.135875 573.45582 71.28125 574.21875 C 71.42597 574.97758 70.851388 578.42584 70 581.875 C 69.149678 585.31901 68.46947 588.70921 68.46875 589.40625 C 68.46803 592.91307 62.991832 611.58027 61.34375 613.71875 C 60.73751 614.50331 59.516362 616.75324 58.625 618.71875 C 57.734358 620.68426 56.343412 623.15311 55.53125 624.21875 C 54.996859 624.91798 54.59719 625.63121 54.34375 626.25 L 52.46875 628.4375 C 52.44066 628.4702 54.128348 626.86943 52.40625 628.5625 C 52.02807 631.64615 54.40556 632.47246 54.875 633.5 C 55.720282 635.34916 54.331801 636.57853 47.59375 643.8125 C 45.484866 646.08071 45.461628 646.08947 46.71875 646.96875 C 48.713874 648.36386 52.40382 653.71738 52.6875 656 C 56.406306 661.16138 56.305798 660.86981 57.375 663.09375 C 57.93228 664.2747 58.782 665.25 59.25 665.25 C 59.71872 665.25 61.380858 665.87845 62.9375 666.65625 C 64.952784 667.66402 67.311516 668.20348 71.15625 668.5 C 74.125534 668.73063 78.568126 669.30841 81.03125 669.8125 C 86.444938 670.92035 99.792996 671.13347 106.875 670.21875 C 113.92526 669.31166 119.66472 668.18129 120.34375 667.5625 C 120.65767 667.27708 121.41349 667.03125 122.03125 667.03125 C 122.64901 667.03125 124.35018 666.35657 125.8125 665.5 C 127.84794 664.30463 128.46875 663.57279 128.46875 662.40625 C 128.46875 660.68579 129.63527 656.26282 130.34375 655.3125 C 130.60168 654.96654 130.95984 653.46924 131.15625 652 C 131.23605 651.40439 131.30986 650.95924 131.34375 650.625 C 131.73745 650.14691 132.70323 646.97318 132.84375 646.125 C 132.97534 646.01566 133.10085 645.9131 133.28125 645.75 C 134.40949 644.72761 134.40229 644.56085 133.46875 643.53125 C 133.01574 643.03128 132.4634 642.63123 132.09375 642.5 C 131.55458 641.81121 129.53943 639.78596 128.40625 638.875 C 125.01145 636.14656 124.71413 634.9293 126.78125 632.71875 L 128.4375 630.9375 L 127 629.03125 C 126.21232 627.98724 124.83246 625.39569 123.9375 623.25 C 123.04182 621.10431 121.80958 618.62825 121.1875 617.75 C 120.56558 616.87793 120.06682 615.78751 120.0625 615.34375 C 120.0467 613.91878 118.26204 609.35421 117.375 608.46875 C 116.74379 607.28076 116.11284 605.77131 115.5 604.15625 C 113.37332 594.98763 107.74421 574.96526 106.53125 573.4375 C 105.40085 572.09284 102.71871 563.06767 102.09375 558.53125 C 101.79203 556.34232 101.4698 552.09848 101.375 549.125 L 101.1875 543.75 L 103.28125 543.03125 C 104.42749 542.63856 106.37263 542.29984 107.59375 542.28125 C 108.81487 542.26255 111.18948 541.95525 112.875 541.59375 C 115.89684 540.94613 115.92754 540.89603 115.5625 539.03125 C 115.35874 537.99444 114.88323 536.52014 114.5 535.75 C 114.32019 535.38708 114.13865 535.17496 114 535.03125 C 113.9716 534.99595 113.94495 534.94465 113.90625 534.90625 C 113.85445 534.86725 113.82225 534.84905 113.78125 534.84375 C 113.3107 534.43576 112.4428 533.9245 112 533.90625 C 111.87884 533.90125 110.79777 533.94325 110.15625 533.96875 C 110.09765 533.83403 109.98706 533.70406 109.84375 533.53125 C 109.47511 533.09161 108.99264 531.96958 108.75 531.0625 C 108.39144 529.7302 107.60955 529.16901 104.71875 528.125 C 103.52499 527.6944 102.43947 527.38956 101.71875 527.21875 C 101.57675 526.05118 100.875 523.29174 100.875 523.03125 C 100.875 522.79039 100.8372 521.00973 100.6875 520.0625 C 101.34706 517.85813 102.4456 511.64548 103 506.53125 C 103.21456 504.55133 103.78589 501.73365 104.28125 500.25 C 105.52037 496.52903 109.11828 488.9991 109.875 488.53125 C 110.21719 488.32147 110.32714 487.93281 110.15625 487.65625 C 109.98489 487.38238 110.44297 485.61461 111.15625 483.75 C 112.35217 480.62516 112.36729 480.30612 111.46875 479.40625 C 110.93307 478.86586 109.3523 478.22754 107.9375 478 C 106.76822 477.81138 105.96929 477.69333 105.40625 477.6875 C 105.28452 477.6865 104.24159 478.07691 103.34375 478.4375 C 102.99071 478.3476 102.5573 478.5297 102.3125 478.875 C 102.3035 478.879 102.22235 478.90355 102.21875 478.90625 C 102.10288 478.98145 102.1115 479.05356 102.1875 479.09375 C 102.1175 479.26936 102.0897 479.46374 102.125 479.6875 C 102.32444 480.95185 101.9705 481.625 101.09375 481.625 C 100.34351 481.625 100.17647 481.24098 100.4375 479.9375 C 100.72478 478.5043 100.55038 478.13326 99.1875 477.59375 C 97.847578 477.06302 97.00731 476.84746 96.46875 476.9375 C 96.145412 476.9915 95.917429 477.1675 95.75 477.4375 C 95.66649 477.57274 95.69185 477.68185 95.78125 477.78125 C 95.531532 478.55347 95.156832 479.88674 95.03125 480 C 93.282368 481.58455 92.46875 481.26332 92.46875 479.03125 C 92.46875 477.92958 92.335359 476.92848 92.1875 476.78125 C 92.05466 476.64798 91.686856 476.59095 91.1875 476.59375 C 89.783498 476.60075 86.80961 478.98253 86.65625 479.625 C 86.65325 479.6393 86.656812 479.64385 86.65625 479.65625 C 86.493458 479.81679 86.327464 480.00897 86.125 480.28125 C 85.011158 481.77829 83.21067 480.5587 82.96875 478.125 C 82.83195 476.74945 82.47567 476.25 81.65625 476.25 z M 81.0625 477.34375 C 81.62091 477.21405 81.99381 477.60216 82.03125 478.34375 C 82.17885 481.23384 82.334628 481.49883 84.0625 481.84375 C 86.390984 482.30397 87.65625 481.79425 87.65625 480.375 C 87.65625 480.11691 87.62795 479.91649 87.59375 479.75 C 88.163853 479.19879 89.08557 477.81656 89.53125 477.71875 C 91.333412 477.32234 91.56925 477.75765 91.28125 480.59375 C 91.17181 481.67071 91.305898 481.70542 94.125 481.8125 C 95.440442 481.8557 95.80027 481.58018 95.96875 480.40625 C 96.02265 480.02858 96.26743 478.60371 96.34375 478.03125 C 96.41455 478.03925 96.483019 478.03125 96.5625 478.03125 C 98.077382 478.03125 99.21875 479.46473 99.21875 481.34375 C 99.21875 482.62972 99.472288 482.84375 101.125 482.84375 C 103.49668 482.84375 104.26481 480.99681 103.90625 479.125 C 103.90425 479.1143 103.90825 479.10415 103.90625 479.09375 C 104.10122 479.06555 104.28307 479.0375 104.34375 479 C 104.86215 478.67568 110.25782 479.59435 110.9375 480.125 C 111.18806 480.32062 109.40826 484.81833 107.0625 489.9375 C 105.9537 492.36324 105.0625 494.57436 105.0625 494.875 C 105.0625 495.76046 104.53048 495.53278 102.15625 493.625 C 99.661446 491.62346 95.627302 489.4375 94.4375 489.4375 C 92.921898 489.4375 93.710386 490.38619 95.84375 491.15625 C 97.036792 491.58764 98.577088 492.4571 99.28125 493.0625 C 99.986132 493.66689 101.3513 494.83566 102.3125 495.65625 L 104.0625 497.125 L 102.71875 501.375 C 101.41483 505.51296 101.34017 505.625 99.40625 505.625 C 98.314728 505.625 96.488122 505.26567 95.375 504.8125 C 94.261878 504.35871 92.387022 503.8086 91.1875 503.59375 C 89.072856 503.21177 89.00372 503.25808 88.625 505.28125 C 87.670998 510.36541 92.303466 515.90813 98.6875 517.3125 C 100.1239 517.62962 100.33207 517.94178 100.09375 519.1875 C 99.99435 519.70494 100.01485 520.19269 100.09375 520.53125 C 99.869348 521.50835 99.65625 522.8243 99.65625 523.03125 C 99.65625 523.28381 100.66133 525.90184 101.28125 527.125 C 101.18285 527.1094 101.0627 527.0634 101 527.0625 C 100.9329 527.06156 100.89556 527.07655 100.875 527.09375 C 100.389 527.49735 102.81142 529.03125 103.9375 529.03125 C 105.96502 529.03125 106.86197 529.79589 108.03125 532.4375 C 108.72461 534.00352 109.49916 534.95041 109.875 534.71875 C 110.22267 534.73425 111.15962 534.58253 112.0625 534.71875 C 112.66633 534.80985 113.21359 535.06696 113.59375 535.21875 C 113.54385 535.66086 113.65615 536.45732 114.0625 537.4375 C 115.03306 539.77778 114.47156 540.4375 111.46875 540.4375 C 107.82267 540.4375 102.2975 542.01393 100.84375 543.46875 C 99.693908 544.61572 99.6243 545.17544 99.9375 550.8125 C 100.31839 557.66449 100.98931 561.72116 102.71875 567.4375 C 103.92406 572.83095 112.9202 600.08052 115.46875 606.96875 C 115.74595 608.1334 115.90794 608.80698 116.21875 610.125 C 116.69971 612.22024 116.77764 611.79909 117.25 612.34375 C 117.72232 612.89047 118.29941 614.12181 118.53125 615.09375 C 118.76309 616.05849 119.43149 617.56404 120.03125 618.40625 C 120.63101 619.24845 121.76834 621.48877 122.5625 623.375 C 123.35738 625.26123 124.61511 627.47646 125.34375 628.3125 C 126.07239 629.15471 126.65625 630.08568 126.65625 630.375 C 126.65625 630.66226 125.5712 631.86713 124.25 633.0625 C 122.9288 634.25787 122.17975 635.24897 122.59375 635.25 C 123.00703 635.257 123.85243 635.98426 124.46875 636.90625 C 125.32881 638.18786 130.24181 642.13772 131.75 642.71875 C 131.75089 642.72157 131.78031 642.71589 131.78125 642.71875 C 131.85272 642.93594 132.04121 643.25637 132.375 643.625 C 132.99087 644.30803 133.14324 644.84375 132.75 644.84375 C 132.40104 644.84375 132.13962 645.23539 132.09375 645.71875 C 131.6 646.94358 130.6514 650.39675 130.5 651.15625 C 130.03587 652.12054 129.42427 654.01128 129.03125 655.65625 C 128.59853 657.47761 128.02 659.21722 127.75 659.53125 C 127.48022 659.84837 127.25 660.81785 127.25 661.71875 C 127.25 662.92132 126.74345 663.67226 125.25 664.59375 C 124.13832 665.27754 122.6997 665.84375 122.0625 665.84375 C 121.42458 665.84375 120.65695 666.08325 120.34375 666.375 C 120.03077 666.66947 116.67034 667.36289 112.875 667.90625 C 109.07988 668.44576 104.24425 669.17089 102.15625 669.53125 C 98.130004 670.23005 86.640746 669.80608 82.8125 668.8125 C 81.627378 668.50981 77.31318 667.88846 73.21875 667.4375 C 68.355142 666.89696 64.655186 666.13649 62.59375 665.25 C 59.234944 663.80547 59.228504 663.7571 51.6875 650.96875 C 50.272698 648.56463 48.66068 646.35454 48.125 646.03125 C 47.240838 645.49969 47.268919 645.40031 48.25 644.8125 C 48.844 644.45317 50.865756 642.21286 52.75 639.84375 C 55.053284 636.94234 56.516028 635.58284 57.21875 635.71875 C 58.524112 635.97099 58.573121 635.04365 57.3125 634 C 56.790507 633.56757 55.877362 631.80656 54.5 630.5 C 54.104086 629.92412 54.182283 628.93179 54.34375 628 C 54.39045 628.0135 54.44195 628.03315 54.5 628.03125 C 55.184002 628.01275 59.209608 621.47585 60.40625 618.4375 C 60.98801 616.96208 61.63855 615.60327 61.84375 615.4375 C 62.981352 614.52167 68.47883 598.40515 68.46875 596 C 68.46575 595.30193 68.83141 592.98944 69.3125 590.84375 C 72.046344 578.6538 72.67211 574.89871 72.96875 568.65625 C 73.148829 564.86114 73.506438 559.84291 73.75 557.53125 C 73.993583 555.2198 73.975099 551.99214 73.71875 550.34375 C 73.461818 548.69536 73.42088 546.0114 73.625 544.40625 C 74.08364 540.77382 73.670144 540.44986 68.375 540.4375 C 63.440112 540.4231 62.46875 540.0763 62.46875 538.3125 C 62.46875 537.68004 62.50441 535.39734 62.375 534.28125 C 62.969173 534.40648 63.796438 534.52871 64.875 534.65625 C 67.993324 535.02382 69.24768 534.4873 69 532.875 C 68.71776 531.03922 69.609461 528.4375 70.53125 528.4375 C 71.457172 528.4375 72.21874 525.83478 72.0625 524.625 C 72.311519 523.55936 72.100001 521.37092 71.46875 519.15625 C 71.186935 518.16269 70.792772 514.95428 70.59375 512.03125 C 70.12863 505.19471 68.041044 497.59122 65.75 494.53125 C 65.008398 493.53873 63.148522 490.24026 61.625 487.1875 C 60.654438 485.24362 59.371202 483.88345 58.5 483.09375 L 57.96875 482.03125 C 58.45997 481.08815 58.731232 480.08534 58.5625 479.8125 C 58.393869 479.54144 58.5084 479.14479 58.84375 478.9375 C 59.179126 478.73027 59.46875 478.8376 59.46875 479.15625 C 59.46875 479.47488 59.971173 479.60044 60.59375 479.4375 C 61.379992 479.2357 62.245178 479.78291 63.4375 481.28125 C 64.382862 482.4622 65.37732 483.4375 65.625 483.4375 C 65.820638 483.4375 65.959653 483.3824 66.03125 483.28125 C 66.875812 483.24245 68.33183 483.03744 68.84375 482.84375 C 70.227592 482.31761 71.128392 480.03345 70.09375 479.6875 C 69.021668 479.33435 70.152479 477.4375 71.4375 477.4375 C 72.907742 477.4375 73.92523 479.19542 73.46875 480.9375 C 73.16131 482.11124 73.357707 482.25 75.59375 482.25 C 78.140394 482.25 78.13754 482.255 78.3125 479 C 78.3463 478.37082 78.755058 477.79906 79.25 477.71875 C 79.74536 477.63805 80.56786 477.46097 81.0625 477.34375 z M 94.75 493.125 C 93.986798 493.1904 93.41255 493.66114 92.84375 494.53125 C 91.224468 497.00126 91.569866 497.84375 94.21875 497.84375 C 95.511872 497.84375 96.978 497.66906 97.5 497.46875 C 98.928482 496.92203 98.695207 494.53698 97.125 493.78125 C 96.322198 493.39525 95.65535 493.15896 95.09375 493.125 C 94.976692 493.118 94.859037 493.116 94.75 493.125 z M 95.5 493.9375 C 95.891954 493.9663 96.94328 494.26831 97.25 494.75 C 97.59776 495.29692 97.19405 496.033 97.34375 496.09375 C 97.986906 496.357 95.926192 496.56029 94.71875 496.59375 C 93.810828 496.61695 93.0625 496.36122 93.0625 496.03125 C 93.1406 495.89563 93.186849 495.33957 93.59375 494.875 C 94.064925 494.33704 94.92832 493.89545 95.5 493.9375 z M 90.625 505.125 C 90.779583 505.1027 90.960635 505.116 91.15625 505.125 C 91.938892 505.1614 93.054098 505.44556 94.5625 506 C 95.882262 506.48664 97.912658 506.81455 99.0625 506.71875 C 101.38522 506.52417 101.88849 507.42771 101.15625 510.34375 C 100.76241 511.91286 100.74597 511.92419 99.84375 510.75 C 99.209284 509.92178 98.84993 509.76328 98.65625 510.28125 C 98.16665 511.58781 96.49311 511.18326 96.84375 509.84375 C 97.016348 509.18117 96.89285 508.625 96.59375 508.625 C 96.29464 508.625 96.0625 509.02445 96.0625 509.5 C 96.0625 512.07709 92.61461 511.13953 92.28125 508.46875 L 92.0625 506.53125 L 91.96875 508.53125 C 91.90825 509.78736 91.605559 510.43054 91.15625 510.28125 C 90.321768 510.00739 89.34248 506.88292 89.75 505.78125 C 89.88365 505.42069 90.16125 505.19177 90.625 505.125 z M 92.75 511.71875 C 92.8038 511.69835 92.8444 511.70875 92.90625 511.71875 C 94.488812 511.9637 95.46875 512.41566 95.46875 512.90625 C 95.46875 513.21836 96.146753 513.30003 96.96875 513.09375 C 98.686672 512.66235 99.713202 514.24213 98.5 515.4375 C 97.944153 515.98751 97.600912 515.88804 96.96875 515.03125 C 96.51875 514.41809 95.89183 514.06567 95.59375 514.25 C 94.950788 514.6464 92.46875 513.03213 92.46875 512.21875 C 92.46875 511.98526 92.588641 511.78 92.75 511.71875 z M 100.25 513.4375 C 100.58 513.4375 100.875 513.95324 100.875 514.59375 C 100.875 515.23508 100.58 515.95136 100.25 516.15625 C 99.909699 516.36527 99.65625 515.83958 99.65625 514.96875 C 99.65625 514.12556 99.920002 513.4375 100.25 513.4375 z M 82.59375 565.78125 C 82.477744 565.81055 82.360041 565.92118 82.1875 566.09375 C 81.736845 566.54677 81.726554 567.34563 82.15625 568.75 C 82.494204 569.85888 82.92412 571.73493 83.125 572.9375 C 83.3266 574.14728 83.994278 575.79478 84.625 576.59375 C 85.255722 577.39273 85.95998 578.84776 86.1875 579.8125 C 86.41502 580.77825 87.175588 582.84574 87.84375 584.4375 C 88.984232 587.14432 88.995034 587.45032 88.09375 589.34375 C 86.049666 593.62689 88.456338 597.84805 93.78125 599.28125 C 99.81198 600.90493 103.7443 598.06915 104.6875 591.4375 C 105.16414 588.08203 103.65562 585.3202 100.5625 583.84375 C 98.875538 583.03757 98.208752 582.17799 97.21875 579.5 C 94.582106 572.36178 92.21008 566.84375 91.75 566.84375 C 90.981038 566.84375 91.152959 567.66727 92.75 571.28125 C 93.575842 573.14689 94.25 575.20231 94.25 575.84375 C 94.25 576.49137 94.768558 578.18217 95.40625 579.59375 C 96.043452 581.00533 96.69039 582.57895 96.84375 583.09375 C 96.99639 583.61164 98.198238 584.35978 99.5 584.75 C 101.19704 585.26068 102.18539 586.01393 102.96875 587.46875 C 104.02787 589.43426 104.01707 589.61092 102.90625 593.03125 C 101.93497 596.01915 101.41333 596.70351 99.53125 597.625 C 93.8598 600.39771 87.205356 594.90256 89.5 589.34375 C 89.908701 588.3502 90.229354 587.41577 90.1875 587.25 C 90.145 587.08422 89.272402 584.65147 88.25 581.84375 C 86.728638 577.66769 84.843352 571.96166 83.09375 566.28125 C 83.00015 565.97798 82.864644 565.81427 82.71875 565.78125 C 82.68225 565.77325 82.63245 565.77125 82.59375 565.78125 z M 111.375 590.46875 C 111.4417 590.62247 111.94431 592.60185 112.75 595.8125 C 111.83598 592.64763 111.27929 590.24833 111.375 590.46875 z M 55.875 660.5625 C -37.437726 925.63729 9.2186374 793.09989 55.875 660.5625 z " + transform="matrix(0.00138889,0,0,-9.7125097e-4,0.12462536,0.91603164)" + id="path3027-3" /> + <path + style="fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:1.01015258;stroke-miterlimit:4;stroke-dasharray:none" + d="m 375.768,484.68313 c -0.58247,-0.58247 -0.90846,-2.05238 -0.91003,-4.10334 -8.2e-4,-1.06899 -0.16174,-2.22274 -0.38432,-2.75546 -0.49283,-1.17949 -0.30225,-1.72523 0.85828,-2.45784 1.89922,-1.19892 4.84337,-1.38306 8.03123,-0.50228 1.68739,0.46621 2.35401,1.27623 2.34839,2.85356 -0.006,1.56899 -0.79729,2.89384 -2.59446,4.34168 -3.04669,2.45446 -6.34189,3.63087 -7.34909,2.62368 z" + id="path3092" + inkscape:connector-curvature="0" + transform="matrix(0.00138889,0,0,-9.7125097e-4,0.12462536,0.91603164)" /> + <path + style="fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:1.01015258;stroke-miterlimit:4;stroke-dasharray:none" + d="m 379.79546,465.38839 c -0.5595,-0.61823 -0.34154,-0.99442 0.48465,-0.83649 0.73188,0.13991 0.8864,-0.42613 0.25856,-0.94719 -0.20834,-0.17291 -0.37881,-0.43392 -0.37881,-0.58002 0,-0.47248 1.21484,0.86004 1.37847,1.51201 0.30384,1.2106 -0.89007,1.79403 -1.74287,0.85169 z" + id="path3094" + inkscape:connector-curvature="0" + transform="matrix(0.00138889,0,0,-9.7125097e-4,0.12462536,0.91603164)" /> + </g> + </g> + </g> +</svg> diff --git a/web/poster/poster.svg b/web/poster/poster.svg new file mode 100644 index 0000000000000000000000000000000000000000..b67cd695c65e4fd9050fac3806c3b0e7fab7e0c6 --- /dev/null +++ b/web/poster/poster.svg @@ -0,0 +1,1505 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + sodipodi:docname="poster.svg" + inkscape:version="0.48.4 r9939" + version="1.1" + id="svg2" + height="1052.3622047" + width="744.09448819"> + <defs + id="defs4"> + <filter + id="filter4549" + y="0" + height="1" + inkscape:menu-tooltip="Edges are partly feathered out" + inkscape:menu="Blurs" + inkscape:label="Apparition" + color-interpolation-filters="sRGB" + x="0" + width="1"> + <feGaussianBlur + id="feGaussianBlur4551" + stdDeviation="5" + result="result6" /> + <feComposite + id="feComposite4553" + in2="result6" + result="result8" + in="SourceGraphic" + operator="atop" /> + <feComposite + id="feComposite4555" + in2="SourceAlpha" + result="result9" + operator="over" + in="result8" /> + <feColorMatrix + id="feColorMatrix4557" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 " + result="result10" /> + <feBlend + id="feBlend4559" + in2="result6" + in="result10" + mode="normal" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4597" /> + <feMorphology + id="feMorphology4599" + radius="4" + in="fbSourceGraphic" + result="result0" /> + <feGaussianBlur + id="feGaussianBlur4601" + in="result0" + stdDeviation="8" + result="result91" /> + <feComposite + id="feComposite4603" + in2="result91" + operator="in" + in="fbSourceGraphic" /> + </filter> + <filter + id="filter4561" + y="0" + height="1" + inkscape:menu-tooltip="Edges are partly feathered out" + inkscape:menu="Blurs" + inkscape:label="Apparition" + color-interpolation-filters="sRGB" + x="0" + width="1"> + <feGaussianBlur + id="feGaussianBlur4563" + stdDeviation="5" + result="result6" /> + <feComposite + id="feComposite4565" + in2="result6" + result="result8" + in="SourceGraphic" + operator="atop" /> + <feComposite + id="feComposite4567" + in2="SourceAlpha" + result="result9" + operator="over" + in="result8" /> + <feColorMatrix + id="feColorMatrix4569" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 " + result="result10" /> + <feBlend + id="feBlend4571" + in2="result6" + in="result10" + mode="normal" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4613" /> + <feMorphology + id="feMorphology4615" + radius="4" + in="fbSourceGraphic" + result="result0" /> + <feGaussianBlur + id="feGaussianBlur4617" + in="result0" + stdDeviation="8" + result="result91" /> + <feComposite + id="feComposite4619" + in2="result91" + operator="in" + in="fbSourceGraphic" /> + </filter> + <filter + id="filter4573" + y="0" + height="1" + inkscape:menu-tooltip="Edges are partly feathered out" + inkscape:menu="Blurs" + inkscape:label="Apparition" + color-interpolation-filters="sRGB" + x="0" + width="1"> + <feGaussianBlur + id="feGaussianBlur4575" + stdDeviation="5" + result="result6" /> + <feComposite + id="feComposite4577" + in2="result6" + result="result8" + in="SourceGraphic" + operator="atop" /> + <feComposite + id="feComposite4579" + in2="SourceAlpha" + result="result9" + operator="over" + in="result8" /> + <feColorMatrix + id="feColorMatrix4581" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 " + result="result10" /> + <feBlend + id="feBlend4583" + in2="result6" + in="result10" + mode="normal" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4621" /> + <feMorphology + id="feMorphology4623" + radius="4" + in="fbSourceGraphic" + result="result0" /> + <feGaussianBlur + id="feGaussianBlur4625" + in="result0" + stdDeviation="8" + result="result91" /> + <feComposite + id="feComposite4627" + in2="result91" + operator="in" + in="fbSourceGraphic" /> + </filter> + <filter + id="filter4585" + y="0" + height="1" + inkscape:menu-tooltip="Edges are partly feathered out" + inkscape:menu="Blurs" + inkscape:label="Apparition" + color-interpolation-filters="sRGB" + x="0" + width="1"> + <feGaussianBlur + id="feGaussianBlur4587" + stdDeviation="5" + result="result6" /> + <feComposite + id="feComposite4589" + in2="result6" + result="result8" + in="SourceGraphic" + operator="atop" /> + <feComposite + id="feComposite4591" + in2="SourceAlpha" + result="result9" + operator="over" + in="result8" /> + <feColorMatrix + id="feColorMatrix4593" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 " + result="result10" /> + <feBlend + id="feBlend4595" + in2="result6" + in="result10" + mode="normal" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4605" /> + <feMorphology + id="feMorphology4607" + radius="4" + in="fbSourceGraphic" + result="result0" /> + <feGaussianBlur + id="feGaussianBlur4609" + in="result0" + stdDeviation="8" + result="result91" /> + <feComposite + id="feComposite4611" + in2="result91" + operator="in" + in="fbSourceGraphic" /> + </filter> + <filter + id="filter4549-3" + y="-0.25" + height="1.5" + inkscape:menu-tooltip="Gives a fluid and wavy expressionist drawing effect to images" + inkscape:menu="Image effects" + inkscape:label="Liquid drawing" + color-interpolation-filters="sRGB" + x="-0.25" + width="1.5"> + <feGaussianBlur + id="feGaussianBlur4551-0" + stdDeviation="5" + result="result6" /> + <feComposite + id="feComposite4553-3" + in2="result6" + result="result8" + in="SourceGraphic" + operator="atop" /> + <feComposite + id="feComposite4555-1" + in2="SourceAlpha" + result="result9" + operator="over" + in="result8" /> + <feColorMatrix + id="feColorMatrix4557-0" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 " + result="result10" /> + <feBlend + id="feBlend4559-5" + in2="result6" + in="result10" + mode="normal" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4597-0" /> + <feMorphology + id="feMorphology4599-3" + radius="4" + in="fbSourceGraphic" + result="result0" /> + <feGaussianBlur + id="feGaussianBlur4601-0" + in="result0" + stdDeviation="8" + result="result91" /> + <feComposite + id="feComposite4603-7" + in2="result91" + operator="in" + in="fbSourceGraphic" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix5344" /> + <feGaussianBlur + id="feGaussianBlur5346" + result="result11" + stdDeviation="2" + in="fbSourceGraphic" /> + <feGaussianBlur + id="feGaussianBlur5348" + in="fbSourceGraphic" + stdDeviation="0.5" + result="result8" /> + <feTurbulence + id="feTurbulence5350" + result="result9" + baseFrequency="0.08" + numOctaves="1" + type="fractalNoise" /> + <feDisplacementMap + id="feDisplacementMap5352" + in2="result11" + result="result10" + xChannelSelector="G" + scale="100" + yChannelSelector="R" /> + <feConvolveMatrix + id="feConvolveMatrix5354" + bias="0" + result="result0" + preserveAlpha="true" + targetY="1" + targetX="1" + divisor="1" + in="result10" + kernelMatrix="1 1 1 1 -8 1 1 1 1 " + order="3 3" /> + <feColorMatrix + id="feColorMatrix5356" + in="result0" + values="0 -6 0 0 1 0 -6 0 0 1 0 -6 0 0 1 0 0 0 1 0 " + result="result3" /> + <feComposite + id="feComposite5358" + in2="fbSourceGraphic" + operator="in" + in="result3" + result="fbSourceGraphic" /> + <feBlend + id="feBlend5360" + in2="result8" + mode="multiply" + result="result12" /> + <feGaussianBlur + id="feGaussianBlur5362" + stdDeviation="0.01" + in="result12" + result="result7" /> + <feBlend + id="feBlend5364" + in2="result12" + mode="screen" + result="result92" /> + <feComposite + id="feComposite5366" + in2="fbSourceGraphic" + operator="in" /> + </filter> + <filter + id="filter4561-6" + y="-0.25" + height="1.5" + inkscape:menu-tooltip="Gives a fluid and wavy expressionist drawing effect to images" + inkscape:menu="Image effects" + inkscape:label="Liquid drawing" + color-interpolation-filters="sRGB" + x="-0.25" + width="1.5"> + <feGaussianBlur + id="feGaussianBlur4563-0" + stdDeviation="5" + result="result6" /> + <feComposite + id="feComposite4565-7" + in2="result6" + result="result8" + in="SourceGraphic" + operator="atop" /> + <feComposite + id="feComposite4567-4" + in2="SourceAlpha" + result="result9" + operator="over" + in="result8" /> + <feColorMatrix + id="feColorMatrix4569-4" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 " + result="result10" /> + <feBlend + id="feBlend4571-9" + in2="result6" + in="result10" + mode="normal" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4613-4" /> + <feMorphology + id="feMorphology4615-8" + radius="4" + in="fbSourceGraphic" + result="result0" /> + <feGaussianBlur + id="feGaussianBlur4617-9" + in="result0" + stdDeviation="8" + result="result91" /> + <feComposite + id="feComposite4619-5" + in2="result91" + operator="in" + in="fbSourceGraphic" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix5368" /> + <feGaussianBlur + id="feGaussianBlur5370" + result="result11" + stdDeviation="2" + in="fbSourceGraphic" /> + <feGaussianBlur + id="feGaussianBlur5372" + in="fbSourceGraphic" + stdDeviation="0.5" + result="result8" /> + <feTurbulence + id="feTurbulence5374" + result="result9" + baseFrequency="0.08" + numOctaves="1" + type="fractalNoise" /> + <feDisplacementMap + id="feDisplacementMap5376" + in2="result11" + result="result10" + xChannelSelector="G" + scale="100" + yChannelSelector="R" /> + <feConvolveMatrix + id="feConvolveMatrix5378" + bias="0" + result="result0" + preserveAlpha="true" + targetY="1" + targetX="1" + divisor="1" + in="result10" + kernelMatrix="1 1 1 1 -8 1 1 1 1 " + order="3 3" /> + <feColorMatrix + id="feColorMatrix5380" + in="result0" + values="0 -6 0 0 1 0 -6 0 0 1 0 -6 0 0 1 0 0 0 1 0 " + result="result3" /> + <feComposite + id="feComposite5382" + in2="fbSourceGraphic" + operator="in" + in="result3" + result="fbSourceGraphic" /> + <feBlend + id="feBlend5384" + in2="result8" + mode="multiply" + result="result12" /> + <feGaussianBlur + id="feGaussianBlur5386" + stdDeviation="0.01" + in="result12" + result="result7" /> + <feBlend + id="feBlend5388" + in2="result12" + mode="screen" + result="result92" /> + <feComposite + id="feComposite5390" + in2="fbSourceGraphic" + operator="in" /> + </filter> + <filter + id="filter4573-5" + y="-0.25" + height="1.5" + inkscape:menu-tooltip="Gives a fluid and wavy expressionist drawing effect to images" + inkscape:menu="Image effects" + inkscape:label="Liquid drawing" + color-interpolation-filters="sRGB" + x="-0.25" + width="1.5"> + <feGaussianBlur + id="feGaussianBlur4575-8" + stdDeviation="5" + result="result6" /> + <feComposite + id="feComposite4577-6" + in2="result6" + result="result8" + in="SourceGraphic" + operator="atop" /> + <feComposite + id="feComposite4579-6" + in2="SourceAlpha" + result="result9" + operator="over" + in="result8" /> + <feColorMatrix + id="feColorMatrix4581-0" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 " + result="result10" /> + <feBlend + id="feBlend4583-2" + in2="result6" + in="result10" + mode="normal" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4621-7" /> + <feMorphology + id="feMorphology4623-3" + radius="4" + in="fbSourceGraphic" + result="result0" /> + <feGaussianBlur + id="feGaussianBlur4625-0" + in="result0" + stdDeviation="8" + result="result91" /> + <feComposite + id="feComposite4627-1" + in2="result91" + operator="in" + in="fbSourceGraphic" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix5392" /> + <feGaussianBlur + id="feGaussianBlur5394" + result="result11" + stdDeviation="2" + in="fbSourceGraphic" /> + <feGaussianBlur + id="feGaussianBlur5396" + in="fbSourceGraphic" + stdDeviation="0.5" + result="result8" /> + <feTurbulence + id="feTurbulence5398" + result="result9" + baseFrequency="0.08" + numOctaves="1" + type="fractalNoise" /> + <feDisplacementMap + id="feDisplacementMap5400" + in2="result11" + result="result10" + xChannelSelector="G" + scale="100" + yChannelSelector="R" /> + <feConvolveMatrix + id="feConvolveMatrix5402" + bias="0" + result="result0" + preserveAlpha="true" + targetY="1" + targetX="1" + divisor="1" + in="result10" + kernelMatrix="1 1 1 1 -8 1 1 1 1 " + order="3 3" /> + <feColorMatrix + id="feColorMatrix5404" + in="result0" + values="0 -6 0 0 1 0 -6 0 0 1 0 -6 0 0 1 0 0 0 1 0 " + result="result3" /> + <feComposite + id="feComposite5406" + in2="fbSourceGraphic" + operator="in" + in="result3" + result="fbSourceGraphic" /> + <feBlend + id="feBlend5408" + in2="result8" + mode="multiply" + result="result12" /> + <feGaussianBlur + id="feGaussianBlur5410" + stdDeviation="0.01" + in="result12" + result="result7" /> + <feBlend + id="feBlend5412" + in2="result12" + mode="screen" + result="result92" /> + <feComposite + id="feComposite5414" + in2="fbSourceGraphic" + operator="in" /> + </filter> + <filter + id="filter4585-6" + y="-0.25" + height="1.5" + inkscape:menu-tooltip="Gives a fluid and wavy expressionist drawing effect to images" + inkscape:menu="Image effects" + inkscape:label="Liquid drawing" + color-interpolation-filters="sRGB" + x="-0.25" + width="1.5"> + <feGaussianBlur + id="feGaussianBlur4587-5" + stdDeviation="5" + result="result6" /> + <feComposite + id="feComposite4589-1" + in2="result6" + result="result8" + in="SourceGraphic" + operator="atop" /> + <feComposite + id="feComposite4591-1" + in2="SourceAlpha" + result="result9" + operator="over" + in="result8" /> + <feColorMatrix + id="feColorMatrix4593-6" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 " + result="result10" /> + <feBlend + id="feBlend4595-4" + in2="result6" + in="result10" + mode="normal" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4605-7" /> + <feMorphology + id="feMorphology4607-8" + radius="4" + in="fbSourceGraphic" + result="result0" /> + <feGaussianBlur + id="feGaussianBlur4609-9" + in="result0" + stdDeviation="8" + result="result91" /> + <feComposite + id="feComposite4611-9" + in2="result91" + operator="in" + in="fbSourceGraphic" + result="fbSourceGraphic" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix5416" /> + <feGaussianBlur + id="feGaussianBlur5418" + result="result11" + stdDeviation="2" + in="fbSourceGraphic" /> + <feGaussianBlur + id="feGaussianBlur5420" + in="fbSourceGraphic" + stdDeviation="0.5" + result="result8" /> + <feTurbulence + id="feTurbulence5422" + result="result9" + baseFrequency="0.08" + numOctaves="1" + type="fractalNoise" /> + <feDisplacementMap + id="feDisplacementMap5424" + in2="result11" + result="result10" + xChannelSelector="G" + scale="100" + yChannelSelector="R" /> + <feConvolveMatrix + id="feConvolveMatrix5426" + bias="0" + result="result0" + preserveAlpha="true" + targetY="1" + targetX="1" + divisor="1" + in="result10" + kernelMatrix="1 1 1 1 -8 1 1 1 1 " + order="3 3" /> + <feColorMatrix + id="feColorMatrix5428" + in="result0" + values="0 -6 0 0 1 0 -6 0 0 1 0 -6 0 0 1 0 0 0 1 0 " + result="result3" /> + <feComposite + id="feComposite5430" + in2="fbSourceGraphic" + operator="in" + in="result3" + result="fbSourceGraphic" /> + <feBlend + id="feBlend5432" + in2="result8" + mode="multiply" + result="result12" /> + <feGaussianBlur + id="feGaussianBlur5434" + stdDeviation="0.01" + in="result12" + result="result7" /> + <feBlend + id="feBlend5436" + in2="result12" + mode="screen" + result="result92" /> + <feComposite + id="feComposite5438" + in2="fbSourceGraphic" + operator="in" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.49497475" + inkscape:cx="441.6874" + inkscape:cy="528.34987" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="1366" + inkscape:window-height="721" + inkscape:window-x="-2" + inkscape:window-y="24" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g3257" + transform="translate(61.099361,200.3118)"> + <g + transform="translate(64.649763,-153.54319)" + id="g3248"> + <g + id="g3202" + transform="translate(-10,14.999996)"> + <g + transform="translate(-74.28572,-52.857144)" + id="g3150"> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220.3557,785.92864 -139.543496,80.56547 393.959496,0" + id="path3130" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 473.61191,866.03514 139.5435,-80.56547 -393.9595,0" + id="path3130-8" + inkscape:connector-curvature="0" /> + </g> + <path + inkscape:connector-curvature="0" + id="path3154" + d="m 206.16032,814.64712 141.81634,-81.8777" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + <path + style="fill:#999999;fill-opacity:1;stroke:none" + d="m 3.901435,826.15591 c 1.549826,-1.06373 31.971124,-18.74675 67.602884,-39.29559 l 64.785021,-37.36152 97.35783,0.36701 97.35784,0.36701 -67.44137,38.92857 -67.44138,38.92858 -97.51935,0 c -82.439902,0 -97.083614,-0.29907 -94.701475,-1.93406 z" + id="path3208" + inkscape:connector-curvature="0" /> + <path + style="fill:#ffffff;fill-opacity:1;stroke:none" + d="m 206.7193,825.32967 c 2.35714,-1.49169 32.79327,-19.1641 67.63585,-39.27202 l 63.35013,-36.55986 92.05606,0.36752 92.05606,0.36751 -24.6919,14.20089 c -13.58056,7.81048 -43.93141,25.32834 -67.44637,38.92857 l -42.75446,24.72769 -92.24554,-0.0241 c -91.15912,-0.0238 -92.19507,-0.056 -87.95983,-2.73623 z" + id="path3210" + inkscape:connector-curvature="0" /> + </g> + <path + transform="matrix(0.78309797,0,0,0.78309797,139.5213,-67.459932)" + inkscape:connector-curvature="0" + id="path4490-9" + d="m 99.277667,905.74418 c -13.43643,-2.09817 -14.63585,-2.83972 -20.73411,-12.81917 -2.91656,-4.77278 -5.30283,-9.10387 -5.30283,-9.62466 0,-0.5208 2.04556,-2.83243 4.54569,-5.13697 2.50012,-2.30453 4.54568,-4.44993 4.54568,-4.76755 0,-0.31761 -0.9161,-1.74211 -2.03578,-3.16554 -1.78003,-2.26295 -1.85399,-2.79058 -0.58874,-4.19995 2.37267,-2.64293 9.3291,-17.35827 11.02894,-23.33015 6.14297,-21.58146 10.095633,-59.88779 6.54584,-63.43758 -0.6667,-0.6667 -3.16683,-1.21218 -5.55584,-1.21218 -3.90592,0 -4.34366,-0.24318 -4.34366,-2.41314 0,-1.32723 0.56822,-2.39204 1.2627,-2.36625 3.50263,0.13008 4.79822,-0.43657 4.79822,-2.09861 0,-1.00498 0.6674,-2.70961 1.4831,-3.78807 1.88985,-2.4986 1.89274,-5.64536 0.0167,-18.12328 -1.54971,-10.30725 -3.92248,-16.49104 -9.53346,-24.84559 -3.42892,-5.10553 -2.57454,-7.74124 1.32257,-4.08009 1.79175,1.68325 3.08622,2.0908 5.06415,1.59437 2.54868,-0.63968 3.09046,-1.47956 2.73253,-4.23595 -0.30659,-2.36102 1.72277,-1.2751 2.35971,1.26269 0.84981,3.3859 4.358793,3.3859 5.646113,0 1.15688,-3.04282 2.60208,-3.26247 3.32315,-0.50508 0.73458,2.80903 5.07931,2.65465 7.10726,-0.25254 l 1.58545,-2.27284 0.053,2.27284 c 0.0402,1.72423 0.65044,2.27285 2.52817,2.27285 1.36135,0 2.94508,-0.7955 3.51939,-1.76777 0.95298,-1.61333 1.10003,-1.56921 1.68324,0.50508 0.74042,2.63344 4.89223,3.17395 5.81946,0.75761 0.61174,-1.59417 5.69562,-2.15511 5.69562,-0.62844 0,0.48773 -0.73588,2.64693 -1.63529,4.79822 -0.8994,2.15129 -2.03477,4.96104 -2.52303,6.24387 l -0.88775,2.33244 -4.99731,-2.80913 c -2.74853,-1.54502 -5.35019,-2.45626 -5.78147,-2.02497 -0.43129,0.43129 -0.1677,1.16515 0.58575,1.63081 1.07173,0.66236 0.90046,1.09789 -0.78687,2.00092 -1.18623,0.63485 -2.15678,2.04741 -2.15678,3.13903 0,2.90099 5.82119,2.80881 6.90066,-0.10927 0.75342,-2.03668 0.83386,-2.0436 2.93689,-0.25254 2.07415,1.76647 2.09728,1.99915 0.56765,5.71068 -1.30675,3.17072 -1.97954,3.76326 -3.72705,3.28245 -1.17285,-0.32269 -3.83709,-0.87186 -5.92053,-1.22036 -3.77527,-0.6315 -3.78807,-0.62172 -3.78807,2.89477 0,4.31971 3.147,8.58135 7.7603,10.5089 2.75912,1.15284 3.346,1.91945 3.19293,4.17074 -0.29577,4.35005 1.09164,7.24617 3.79657,7.92506 1.37133,0.34418 3.15429,1.90396 3.96214,3.46617 0.81626,1.57847 2.47895,2.98443 3.74249,3.16463 1.25051,0.17834 2.41309,1.05645 2.5835,1.95136 0.23769,1.24819 -1.06867,1.8983 -5.60983,2.79172 -3.25581,0.64054 -6.45893,1.70389 -7.11804,2.363 -1.64303,1.64303 -1.49165,13.07132 0.27145,20.4923 3.28415,13.8232 16.02038,50.91027 20.14773,58.66888 l 4.35264,8.1821 -1.89003,2.5946 -1.89003,2.5946 5.06574,4.43138 5.06574,4.43139 -3.01125,9.63346 c -3.48707,11.15567 -3.52556,11.1865 -16.87441,13.51435 -10.4107,1.81548 -24.90881,1.77792 -36.906653,-0.0956 l 0,-4e-5 z m 27.041983,-69.56407 c 1.89343,-1.89342 2.47947,-3.54741 2.47947,-6.99785 0,-3.80619 -0.47165,-4.88818 -2.99229,-6.86444 -1.83568,-1.43924 -4.10093,-5.05341 -5.86049,-9.35032 -1.57751,-3.85234 -3.46978,-7.20478 -4.20505,-7.44987 -0.82244,-0.27415 -1.11678,0.0888 -0.76494,0.94334 0.31455,0.76393 1.56741,4.5078 2.78413,8.31972 1.81704,5.69268 2.82013,7.28944 5.61528,8.93862 2.63979,1.55751 3.40305,2.67547 3.40305,4.98444 0,4.045 -4.25071,8.31498 -7.34042,7.3737 -4.34112,-1.3225 -6.02065,-3.66088 -5.40313,-7.52264 0.41787,-2.61325 -0.45141,-6.46962 -3.2531,-14.43155 -3.51698,-9.99468 -5.21655,-12.44991 -5.21655,-7.53594 0,1.06492 1.36239,5.33862 3.02754,9.49713 2.13553,5.33322 2.96508,8.99633 2.81556,12.43275 -0.22383,5.14436 1.09714,7.70929 4.72843,9.1812 3.89802,1.58002 7.64254,1.02169 10.18251,-1.51829 l 0,0 z" + style="fill:#483737;fill-opacity:1;stroke:#241c1c;filter:url(#filter4561-6)" /> + <path + transform="matrix(1.6852156,0,0,1.6852156,-56.547594,-874.77105)" + inkscape:connector-curvature="0" + id="path4492-4" + d="m 164.77504,904.50679 c -2.95637,-0.30904 -4.12705,-1.04093 -5.30331,-3.31556 -2.21555,-4.28441 -1.94128,-5.52539 2.74475,-12.41863 6.58892,-9.69246 9.17854,-17.65576 9.26198,-28.48139 0.0681,-8.83503 -0.0512,-9.316 -4.2304,-17.05828 -3.8606,-7.15204 -4.19185,-8.29601 -3.23856,-11.1845 0.58404,-1.76967 2.67063,-4.77581 4.63687,-6.6803 3.23869,-3.13699 4.11958,-3.45972 9.36454,-3.43092 9.5091,0.0522 20.37167,7.42773 20.34201,13.81189 -0.0265,5.70983 -0.62823,5.99632 -9.95673,4.7409 -4.58302,-0.61678 -8.57843,-0.87575 -8.87869,-0.57549 -1.07992,1.07992 3.86321,8.30117 7.15732,10.4559 2.62822,1.71915 3.80732,3.59894 5.38577,8.58629 5.07523,16.03591 8.20757,23.11092 12.28057,27.7381 4.36373,4.95746 5.53297,7.68177 4.97009,11.5803 -0.31263,2.1653 -1.29463,2.4888 -13.97332,4.60324 -12.72266,2.12177 -21.41378,2.58485 -30.56289,1.62845 z m 26.52228,-24.5176 c 3.07537,-3.07538 2.46502,-5.89093 -2.27284,-10.48467 -2.36124,-2.2894 -4.80805,-5.07168 -5.43737,-6.18285 -0.62932,-1.11117 -1.45833,-1.72162 -1.84225,-1.35656 -1.1957,1.13696 2.2225,6.50875 6.14302,9.6539 2.60951,2.09342 3.55042,3.51531 3.10106,4.68631 -0.91144,2.37517 -5.13107,4.05887 -8.22568,3.28217 -2.27242,-0.57035 -2.47547,-0.93215 -1.52247,-2.71284 1.45943,-2.72697 0.45826,-6.84476 -2.56324,-10.54255 -2.97769,-3.64416 -3.26524,-1.84082 -0.55392,3.47382 1.68828,3.30929 1.80149,4.34763 0.77154,7.07627 -1.49906,3.97148 -0.16763,5.08929 6.0887,5.11183 2.96138,0.0107 4.91986,-0.61124 6.31345,-2.00483 z m -2.20259,-45.21465 c 0.24255,-1.70007 -0.18208,-2.26252 -1.70815,-2.26252 -2.41781,0 -3.56541,1.52796 -2.74782,3.65856 0.86409,2.25177 4.07925,1.24447 4.45597,-1.39604 l 0,0 z m 2.90387,-3.80967 c -2.41299,-2.12285 -7.77235,-3.51057 -7.77235,-2.01252 0,0.79341 7.45829,4.47954 9.09137,4.49325 0.98418,0.008 0.52189,-0.86117 -1.31902,-2.48073 l 0,0 z" + style="fill:#483737;fill-opacity:1;stroke:#241c1c;filter:url(#filter4549-3)" /> + <path + inkscape:connector-curvature="0" + id="path3027" + d="m 266.32874,561.55009 c -6.13008,0.35281 -12.14842,4.15546 -15.90625,11.18753 l -0.93743,4.21827 -0.375,-0.0625 c 0.0126,0.15488 0.12801,0.38864 0.25,0.6875 l -0.125,0.53125 0.40626,0.125 c 2.06207,4.29859 9.73634,16.29404 9.84376,21.40622 0.51805,15.60956 -7.05312,27.86951 -14.65625,37.53128 l 0.8748,3.56241 c -0.10437,9.09755 7.65295,7.28958 11.87504,7.78172 9.86917,2.90656 19.6596,-1.42084 29.5313,-2.40618 4.21272,-2.96319 10.66544,1.9058 14.12504,-3.84349 l -0.625,-4.59407 -3.1248,-7.46872 c -8.49039,-7.14543 -12.46753,-23.20379 -15.99999,-35.87497 0.5765,-0.15446 1.15704,-0.59592 1.71864,-1.40644 1.70712,-4.90707 6.3072,-5.82136 7.03152,-11.8125 0.1489,-13.81291 -11.00347,-15.69728 -17.81251,-18.75005 -1.9872,-0.65645 -4.05073,-0.93009 -6.09408,-0.8125 z m 0.75024,1.06254 c 0.70945,0.0112 1.38528,0.0804 2.03111,0.1875 2.13625,0.35521 4.19185,1.78534 6.6564,1.84401 1.49833,1.1511 7.47743,3.92588 10.03127,6.53076 2.65607,2.85199 4.0068,9.15005 0.93743,12.87494 -4.92336,-1.12432 -13.37687,-1.11402 -17.75001,-2.1879 l 1.78128,3.9063 3.06216,4.59408 c 2.00304,0.0326 4.1292,3.06615 6.21864,3.8435 0.94608,7.096 10.22126,27.6878 8.84376,27.09371 0.49892,1.21699 1.22832,2.83964 1.93752,3.687 2.94912,3.52947 6.60455,5.1758 8.21873,10.78125 l -0.0625,7.21852 c -7.34055,0.94827 -14.60621,2.4731 -21.93754,3.46873 -8.32766,2.45251 -16.55157,1.10785 -24.93756,0.28126 -4.20409,-0.14332 -6.97753,-7.83629 -3.93769,-12.34377 8.27431,-9.56395 15.00991,-25.31354 11.7188,-40.96872 -2.14056,-4.75777 -4.95144,-11.41682 -8.24997,-15.25002 l 0.9684,-4.40669 c 3.2688,-8.69085 9.50248,-11.23221 14.46876,-11.15622 z m 10.21874,7.8435 -3.09384,0.1875 0.40625,0.59375 5.59369,3.03114 3.00024,0.75 -5.8752,-4.56216 -0.0312,0 z m 0.6875,4.34387 -2.96856,0.12502 -0.9684,1.46924 c 0.46008,5.74207 6.0516,1.61749 3.93768,-1.59383 z m -1.4688,1.50014 c 0.79632,0.04 0.34077,3.09394 -0.7812,0.5625 0.32049,-0.41699 0.59754,-0.57174 0.7812,-0.5625 z m -4.84344,7.18764 c 0.5436,0.33729 1.06488,0.65506 1.40616,0.93749 0.18482,0.15296 0.20274,-0.59183 0.46875,0.43751 0.78768,3.04967 -2.5128,2.23835 -2.31263,-0.68751 l 0.4375,-0.68749 z m 3.96864,0.84374 2.59344,0.0937 8.24997,-0.0937 c -1.28304,1.39408 -3.2328,2.3825 -6.0624,2.59356 -1.48751,1.29215 -3.05784,-2.0942 -3.8124,-0.15625 l -1.68767,-1.09344 0.71875,-1.34362 z m -0.0625,3.90631 c 0.79632,0.04 0.37202,3.09394 -0.75024,0.5625 0.32049,-0.417 0.56629,-0.57174 0.75024,-0.5625 z m 2.1564,0.0937 2.46887,0.5 c 5.40577,1.33025 -0.8568,5.29936 0.71875,1.75032 l -2.03111,0.53125 -1.68769,-1.09343 0.53125,-1.68752 z m -6.6564,15.50001 1.00007,2.59356 c 1.61785,6.33204 7.27137,5.90682 9.62504,10.71875 -2.6712,6.17245 -8.93081,7.17323 -12.87497,3.25045 l 1.2816,-1.53101 c 2.29248,-6.21055 -2.10095,-9.03989 -4.3128,-12.62496 0.30385,3.01365 2.0736,5.22625 3.28105,7.49961 2.71583,4.19665 -3.69145,5.6731 -0.18752,9.06254 4.86073,1.89446 12.30899,2.47413 13.71874,-6.87464 -3.29184,-3.2731 -8.29605,-3.93616 -9.87501,-10.12509 l -1.656,-1.96859 z" + style="fill:#000000;filter:url(#filter4585)" /> + <path + sodipodi:nodetypes="scssccsccccccscccscscccccccccccccccccccccccccscccccscsccccccccccccccccccccccccccccccccccsccccccccccccccsccccccsccccccscccsccccccssscccscscscccccsccccccscccsccsccscccccccsccsscccccsccccscsccccccccccccccccccccccccscccscccccccccsccscccscscccccscccccccccscccccccccssccccccccsccccscsscssccccscccccccscsccccccccccccccc" + inkscape:connector-curvature="0" + id="path3027-3" + d="m 195.00877,456.61765 c -2.39545,0 -4.03473,1.0801 -4.40626,2.9375 -0.26712,1.33643 -0.68563,1.875 -1.5625,1.875 -0.94608,0 -1.21875,-0.39855 -1.21875,-1.78125 0,-3.31942 -4.24302,-3.95161 -5.4375,-0.8125 -0.39808,1.04402 -0.37798,1.77542 0.0937,2.34375 0.54558,0.65832 0.41665,0.74378 -0.5625,0.84375 -0.66899,0.0683 -1.94086,0.48658 -2.6875,0.75001 -0.29011,-0.57227 -0.87736,-1.34051 -1.75001,-2.21875 l 0,-0.0312 c -1.52784,-1.53719 -3.09383,-2.2156 -4.34375,-2.125 -1.60704,0.116 -2.72451,1.53241 -2.71875,4.03125 l 1.375,1.5 c 0.20491,0.23865 0.44144,0.49723 0.65625,0.75 0.39605,0.46602 0.77176,0.91162 1,1.25 0.22248,0.32432 0.76774,1.63402 1.1875,2.9375 0.78336,2.43088 3.93309,8.21255 4.78126,8.75 0.26064,0.16473 0.75679,1.77958 1.09375,3.59375 0.33696,1.81416 0.90541,4.1146 1.28125,5.09375 0.37656,0.98223 0.96834,4.47622 1.3125,7.78125 0.3443,3.30605 0.77406,7.32272 0.9375,8.90625 0.0797,0.77246 0.20976,1.36256 0.34375,1.8125 -0.2737,0.77045 -0.6288,2.17011 -0.9375,2.375 -1.386,0.92147 -1.83156,1.65135 -2.625,4.34375 -0.48816,1.65147 -0.78994,1.82436 -3.81251,2.125 -1.80792,0.17967 -2.5609,0.38433 -2.3125,0.625 -0.11271,0.2428 0.11184,2.34957 0.0312,3.46875 -0.29665,4.1184 -0.16863,4.21166 5.65626,4.40625 l 5.21875,0.15625 0.1875,7.09375 c 0.10055,3.90321 -0.19043,10.87467 -0.625,15.46875 -0.43458,4.59716 -0.67662,8.95582 -0.53125,9.71875 0.14472,0.75882 -0.42986,4.20708 -1.28125,7.65625 -0.85032,3.44401 -1.53053,6.83421 -1.53125,7.53125 -7.2e-4,3.50682 -5.47692,22.17402 -7.12501,24.3125 -0.60624,0.78456 -1.82739,3.03448 -2.71875,5 -0.89064,1.96551 -2.28159,4.43436 -3.09375,5.49999 -0.53439,0.69923 -0.93406,1.41247 -1.1875,2.03125 l -1.875,2.1875 c -0.0281,0.0327 1.65959,-1.56807 -0.0625,0.12501 -0.37819,3.08364 1.99931,3.90996 2.46875,4.93749 0.84528,1.84917 -0.5432,3.07853 -7.28126,10.3125 -2.10888,2.26822 -2.13212,2.27698 -0.875,3.15625 1.99513,1.39512 5.68508,6.74863 5.96876,9.03125 3.7188,5.16138 3.6183,4.86982 4.6875,7.09376 0.55728,1.18094 1.407,2.15625 1.875,2.15625 0.46872,0 2.13086,0.62845 3.6875,1.40625 2.01529,1.00777 4.37402,1.54722 8.21876,1.84375 2.96929,0.23062 7.41188,0.80841 9.87501,1.3125 5.41369,1.10784 18.76176,1.32097 25.84377,0.40625 7.05026,-0.90709 12.78973,-2.03747 13.46876,-2.65625 0.31392,-0.28543 1.06974,-0.53125 1.6875,-0.53125 0.61776,0 2.31894,-0.67468 3.78125,-1.53125 2.03545,-1.19538 2.65626,-1.92722 2.65626,-3.09376 0,-1.72046 1.16651,-6.14342 1.875,-7.09375 0.25793,-0.34596 0.61609,-1.84326 0.8125,-3.31249 0.0797,-0.59562 0.15361,-1.04077 0.1875,-1.37501 0.3937,-0.47808 1.35948,-3.65182 1.5,-4.5 0.13159,-0.10937 0.2571,-0.2119 0.4375,-0.375 1.12824,-1.02238 1.12104,-1.18915 0.1875,-2.21875 -0.45301,-0.49997 -1.00535,-0.90002 -1.375,-1.03125 -0.53917,-0.68878 -2.55432,-2.71403 -3.6875,-3.62499 -3.39481,-2.72844 -3.69212,-3.94571 -1.62501,-6.15626 l 1.65626,-1.7813 -1.43751,-1.90624 c -0.78768,-1.04402 -2.16754,-3.63557 -3.0625,-5.78125 -0.89568,-2.14569 -2.12793,-4.62175 -2.75,-5.5 -0.62193,-0.87208 -1.12068,-1.96249 -1.125,-2.40625 -0.0157,-1.42498 -1.80046,-5.98954 -2.6875,-6.875 -0.63121,-1.18799 -1.26216,-2.69744 -1.875,-4.3125 -2.12669,-9.16863 -7.7558,-29.19099 -8.96876,-30.71875 -1.1304,-1.34467 -3.81254,-10.36983 -4.4375,-14.90625 -0.30173,-2.18893 -0.62395,-6.43278 -0.71875,-9.40625 l -0.1875,-5.375 2.09375,-0.71875 c 1.14623,-0.39269 3.09138,-0.73142 4.3125,-0.75 1.22112,-0.0188 3.59573,-0.326 5.28126,-0.6875 3.02184,-0.64763 3.05254,-0.69773 2.6875,-2.5625 -0.20376,-1.03682 -0.67928,-2.51112 -1.0625,-3.28125 -0.17981,-0.36293 -0.36135,-0.57504 -0.5,-0.71875 -0.0284,-0.0353 -0.055,-0.0866 -0.0937,-0.125 -0.0517,-0.039 -0.084,-0.0573 -0.125,-0.0625 -0.47055,-0.40798 -1.33846,-0.91925 -1.78126,-0.9375 -0.12113,-0.005 -1.20223,0.037 -1.84375,0.0625 -0.0586,-0.13473 -0.16919,-0.26469 -0.3125,-0.43751 -0.36864,-0.43963 -0.85112,-1.56167 -1.09375,-2.46875 -0.35857,-1.3323 -1.14045,-1.89348 -4.03125,-2.9375 -1.19377,-0.4306 -2.27929,-0.73543 -3,-0.90625 -0.142,-1.16757 -0.84375,-3.92701 -0.84375,-4.1875 0,-0.24086 -0.0377,-2.02152 -0.18751,-2.96875 0.65957,-2.20437 1.75811,-8.41702 2.31251,-13.53125 0.21456,-1.97992 0.78588,-4.7976 1.28125,-6.28125 1.23912,-3.72097 4.83703,-11.2509 5.59375,-11.71875 0.3422,-0.20977 0.45215,-0.59843 0.28125,-0.875 -0.17135,-0.27387 0.28672,-2.04163 1,-3.90625 1.19592,-3.12483 1.21105,-3.44387 0.3125,-4.34375 -0.53568,-0.54038 -2.11645,-1.17871 -3.53125,-1.40625 -1.16928,-0.18862 -1.96822,-0.30667 -2.53125,-0.3125 -0.12175,-0.001 -1.16467,0.38942 -2.0625,0.75 -0.35304,-0.0899 -0.78645,0.0923 -1.03125,0.4375 -0.009,0.004 -0.0901,0.0285 -0.0937,0.0312 -0.11588,0.0752 -0.10725,0.14732 -0.0312,0.1875 -0.07,0.17562 -0.0977,0.36999 -0.0625,0.59375 0.19943,1.26435 -0.1545,1.9375 -1.03125,1.9375 -0.75025,0 -0.91729,-0.38402 -0.65626,-1.6875 0.28728,-1.4332 0.11288,-1.80423 -1.25,-2.34375 -1.33992,-0.53072 -2.18019,-0.74628 -2.71875,-0.65625 -0.32334,0.054 -0.55132,0.23 -0.71875,0.5 -0.0835,0.13524 -0.0581,0.24435 0.0312,0.34375 -0.24972,0.77223 -0.62442,2.10549 -0.75,2.21875 -1.74889,1.58455 -2.5625,1.26333 -2.5625,-0.96875 0,-1.10167 -0.1334,-2.10277 -0.28125,-2.25 -0.13284,-0.13327 -0.50065,-0.1903 -1.00001,-0.1875 -1.404,0.007 -4.37789,2.38878 -4.53125,3.03125 -0.003,0.0143 5.6e-4,0.0188 0,0.0312 -0.16279,0.16054 -0.32879,0.35273 -0.53125,0.625 -1.11384,1.49704 -2.91433,0.27745 -3.15625,-2.15625 -0.1368,-1.37555 -0.49308,-1.875 -1.3125,-1.875 z m -0.59375,1.09375 c 0.55841,-0.1297 0.93131,0.25842 0.96875,1 0.1476,2.89009 0.30338,3.15508 2.03125,3.5 2.32848,0.46023 3.59375,-0.0495 3.59375,-1.46875 0,-0.25808 -0.0283,-0.45851 -0.0625,-0.625 0.57011,-0.55121 1.49182,-1.93343 1.9375,-2.03125 1.80217,-0.39641 2.03801,0.0389 1.75,2.875 -0.10943,1.07697 0.0246,1.11168 2.84376,1.21875 1.31544,0.0433 1.67527,-0.23232 1.84375,-1.40625 0.0539,-0.37767 0.29868,-1.80253 0.375,-2.37499 0.0708,0.007 0.13927,0 0.21875,0 1.51488,0 2.65625,1.43347 2.65625,3.31249 0,1.28598 0.25354,1.5 1.90626,1.5 2.37167,0 3.13981,-1.84693 2.78125,-3.71875 -0.002,-0.0112 0.003,-0.0207 0,-0.0312 0.19497,-0.0282 0.37682,-0.0562 0.4375,-0.0937 0.5184,-0.32432 5.91407,0.59435 6.59375,1.125 0.25056,0.19563 -1.52924,4.69333 -3.875,9.8125 -1.1088,2.42574 -2,4.63687 -2,4.9375 0,0.88547 -0.53203,0.65778 -2.90625,-1.25 -2.49481,-2.00153 -6.52896,-4.1875 -7.71876,-4.1875 -1.51561,0 -0.72712,0.94869 1.40625,1.71875 1.19304,0.43139 2.73334,1.30085 3.4375,1.90625 0.70488,0.60439 2.07006,1.77317 3.03126,2.59375 l 1.75,1.46875 -1.34375,4.25 c -1.30393,4.13797 -1.37859,4.25 -3.31251,4.25 -1.09152,0 -2.91813,-0.35932 -4.03125,-0.8125 -1.11312,-0.45378 -2.98798,-1.0039 -4.18751,-1.21875 -2.11464,-0.38197 -2.18378,-0.33567 -2.5625,1.6875 -0.954,5.08417 3.67847,10.62688 10.06251,12.03125 1.4364,0.31713 1.64457,0.62928 1.40625,1.875 -0.0994,0.51744 -0.0789,1.00519 0,1.34375 -0.2244,0.9771 -0.4375,2.29305 -0.4375,2.5 0,0.25257 1.00508,2.87059 1.62501,4.09375 -0.0984,-0.0156 -0.21855,-0.0616 -0.28125,-0.0625 -0.0671,-9.3e-4 -0.1045,0.014 -0.125,0.0312 -0.48601,0.4036 1.93641,1.9375 3.0625,1.9375 2.02752,0 2.92447,0.76464 4.09375,3.40625 0.69336,1.56603 1.46791,2.51292 1.84375,2.28125 0.34767,0.0155 1.28462,-0.13622 2.1875,0 0.60383,0.0911 1.1511,0.34822 1.53126,0.5 -0.0499,0.44212 0.0624,1.23858 0.46875,2.21875 0.97055,2.34028 0.40905,3 -2.59376,3 -3.64608,0 -9.17125,1.57643 -10.625,3.03125 -1.14985,1.14698 -1.21946,1.70669 -0.90626,7.34375 0.38089,6.85199 1.05182,10.90867 2.78126,16.625 1.20531,5.39345 10.20146,32.64303 12.75001,39.53125 0.2772,1.16465 0.43919,1.83823 0.75,3.15625 0.48096,2.09524 0.55889,1.67409 1.03125,2.21875 0.47231,0.54673 1.04941,1.77807 1.28125,2.75 0.23184,0.96474 0.90024,2.47029 1.5,3.3125 0.59976,0.8422 1.73709,3.08253 2.53125,4.96875 0.79489,1.88623 2.05261,4.10147 2.78125,4.9375 0.72865,0.84222 1.3125,1.77318 1.3125,2.0625 0,0.28727 -1.08505,1.49213 -2.40625,2.6875 -1.3212,1.19538 -2.07025,2.18648 -1.65625,2.1875 0.41329,0.007 1.25869,0.73427 1.875,1.65625 0.86006,1.28162 5.77306,5.23148 7.28126,5.8125 8.9e-4,0.002 0.0302,-0.003 0.0312,0 0.0715,0.21719 0.25996,0.53763 0.59375,0.90625 0.61586,0.68303 0.76824,1.21875 0.375,1.21875 -0.34896,0 -0.61039,0.39164 -0.65625,0.875 -0.49375,1.22483 -1.44235,4.678 -1.59375,5.4375 -0.46414,0.96429 -1.07574,2.85503 -1.46875,4.5 -0.43272,1.82137 -1.01126,3.56098 -1.28126,3.875 -0.26978,0.31713 -0.5,1.2866 -0.5,2.1875 0,1.20258 -0.50655,1.95352 -2,2.875 -1.11168,0.68379 -2.5503,1.25 -3.1875,1.25 -0.63793,0 -1.40555,0.2395 -1.71875,0.53125 -0.31298,0.29448 -3.67341,0.98789 -7.46875,1.53125 -3.79513,0.53952 -8.63076,1.26464 -10.71876,1.625 -4.02626,0.6988 -15.51552,0.27483 -19.34377,-0.71875 -1.18512,-0.30268 -5.49933,-0.92403 -9.59376,-1.375 -4.86361,-0.54053 -8.56357,-1.30101 -10.62501,-2.1875 -3.35881,-1.44452 -3.36525,-1.4929 -10.90626,-14.28125 -1.4148,-2.40412 -3.02682,-4.61421 -3.5625,-4.9375 -0.88416,-0.53156 -0.85608,-0.63093 0.125,-1.21875 0.594,-0.35932 2.61576,-2.59963 4.50001,-4.96875 2.30328,-2.90141 3.76603,-4.26091 4.46875,-4.125 1.30536,0.25224 1.35437,-0.6751 0.0937,-1.71875 -0.52199,-0.43242 -1.43514,-2.19343 -2.8125,-3.5 -0.39592,-0.57587 -0.31772,-1.56821 -0.15625,-2.5 0.0467,0.0135 0.0982,0.0331 0.15625,0.0312 0.684,-0.0185 4.70961,-6.5554 5.90625,-9.59375 0.58176,-1.47542 1.2323,-2.83422 1.4375,-3 1.13761,-0.91582 6.63509,-17.03235 6.62501,-19.4375 -0.003,-0.69807 0.36266,-3.01056 0.84375,-5.15625 2.73384,-12.18995 3.35961,-15.94503 3.65625,-22.1875 0.18008,-3.79511 0.53769,-8.81333 0.78125,-11.125 0.24359,-2.31145 0.2251,-5.53911 -0.0312,-7.1875 -0.25693,-1.64838 -0.29787,-4.33235 -0.0937,-5.9375 0.45864,-3.63242 0.0451,-3.95638 -5.25,-3.96875 -4.93489,-0.0144 -5.90626,-0.3612 -5.90626,-2.125 0,-0.63246 0.0357,-2.91516 -0.0937,-4.03125 0.59418,0.12523 1.42144,0.24747 2.50001,0.375 3.11832,0.36758 4.37268,-0.16895 4.125,-1.78125 -0.28224,-1.83577 0.60946,-4.4375 1.53125,-4.4375 0.92592,0 1.68749,-2.60272 1.53125,-3.8125 0.24902,-1.06563 0.0375,-3.25407 -0.59375,-5.46875 -0.28181,-0.99356 -0.67598,-4.20197 -0.875,-7.125 -0.46512,-6.83653 -2.55271,-14.44002 -4.84375,-17.5 -0.74161,-0.99252 -2.60148,-4.29098 -4.12501,-7.34375 -0.97056,-1.94387 -2.2538,-3.30405 -3.125,-4.09375 l -0.53125,-1.0625 c 0.49122,-0.9431 0.76248,-1.94591 0.59375,-2.21875 -0.16863,-0.27106 -0.0541,-0.66771 0.28125,-0.875 0.33538,-0.20722 0.625,-0.0999 0.625,0.21875 0,0.31863 0.50242,0.44419 1.125,0.28125 0.78624,-0.2018 1.65143,0.34542 2.84375,1.84375 0.94537,1.18095 1.93983,2.15625 2.18751,2.15625 0.19563,0 0.33465,-0.0551 0.40625,-0.15625 0.84456,-0.0387 2.30058,-0.24381 2.8125,-0.4375 1.38384,-0.52613 2.28464,-2.8103 1.25,-3.15625 -1.07208,-0.35315 0.0587,-2.25 1.34375,-2.25 1.47024,0 2.48773,1.75793 2.03125,3.5 -0.30744,1.17374 -0.11104,1.3125 2.125,1.3125 2.54665,0 2.5438,0.005 2.71876,-3.25 0.0338,-0.62917 0.44255,-1.20093 0.9375,-1.28125 0.49536,-0.0807 1.31786,-0.25777 1.8125,-0.375 z m 13.68751,15.78125 c -0.7632,0.0654 -1.33745,0.53614 -1.90625,1.40625 -1.61929,2.47002 -1.27389,3.3125 1.375,3.3125 1.29312,0 2.75925,-0.17468 3.28125,-0.375 1.42848,-0.54672 1.19521,-2.93177 -0.375,-3.6875 -0.8028,-0.386 -1.46965,-0.62228 -2.03125,-0.65625 -0.11706,-0.007 -0.23472,-0.009 -0.34375,0 z m 0.75,0.8125 c 0.39195,0.0288 1.44328,0.33082 1.75,0.8125 0.34776,0.54693 -0.056,1.283 0.0937,1.34375 0.64316,0.26325 -1.41756,0.46654 -2.625,0.5 -0.90793,0.0233 -1.65625,-0.23252 -1.65625,-0.5625 0.0781,-0.13562 0.12435,-0.69167 0.53125,-1.15625 0.47117,-0.53796 1.33457,-0.97955 1.90625,-0.9375 z m -4.87501,11.1875 c 0.15459,-0.0222 0.33564,-0.009 0.53125,0 0.78265,0.0364 1.89785,0.32057 3.40626,0.875 1.31976,0.48664 3.35016,0.81455 4.5,0.71875 2.32272,-0.19457 2.82599,0.70897 2.09376,3.625 -0.39384,1.56912 -0.41029,1.58044 -1.31251,0.40625 -0.63446,-0.82822 -0.99382,-0.98672 -1.1875,-0.46875 -0.4896,1.30657 -2.16314,0.90202 -1.8125,-0.4375 0.1726,-0.66257 0.0491,-1.21875 -0.25,-1.21875 -0.29911,0 -0.53125,0.39945 -0.53125,0.875 0,2.57709 -3.44789,1.63953 -3.78125,-1.03125 l -0.21875,-1.9375 -0.0937,2 c -0.0605,1.25612 -0.3632,1.89929 -0.81251,1.75 -0.83448,-0.27386 -1.81377,-3.39832 -1.40625,-4.5 0.13365,-0.36056 0.41125,-0.58947 0.875,-0.65625 z m 2.12501,6.59375 c 0.0538,-0.0204 0.0944,-0.01 0.15625,0 1.58256,0.24495 2.5625,0.69692 2.5625,1.1875 0,0.31212 0.678,0.39378 1.5,0.1875 1.71792,-0.4314 2.74445,1.14838 1.53125,2.34375 -0.55585,0.55002 -0.89909,0.45054 -1.53125,-0.40625 -0.45,-0.61316 -1.07692,-0.96557 -1.375,-0.78125 -0.64296,0.3964 -3.125,-1.21787 -3.125,-2.03125 0,-0.23348 0.11988,-0.43875 0.28125,-0.5 z m 7.5,1.71875 c 0.33,0 0.62501,0.51574 0.62501,1.15625 0,0.64133 -0.29501,1.35762 -0.62501,1.5625 -0.3403,0.20903 -0.59375,-0.31667 -0.59375,-1.1875 0,-0.84318 0.26375,-1.53125 0.59375,-1.53125 z m -17.65626,52.34375 c -0.116,0.0292 -0.23371,0.13993 -0.40625,0.3125 -0.45066,0.45303 -0.46095,1.25188 -0.0312,2.65625 0.33795,1.10888 0.76787,2.98493 0.96875,4.1875 0.2016,1.20978 0.86928,2.85728 1.5,3.65625 0.63072,0.79898 1.33498,2.25402 1.5625,3.21875 0.22752,0.96575 0.98809,3.03324 1.65625,4.625 1.14049,2.70683 1.15129,3.01283 0.25,4.90625 -2.04408,4.28314 0.36259,8.5043 5.68751,9.9375 6.03073,1.62368 9.96306,-1.2121 10.90626,-7.84375 0.47663,-3.35547 -1.03188,-6.1173 -4.12501,-7.59375 -1.68696,-0.80617 -2.35375,-1.66576 -3.34375,-4.34375 -2.63665,-7.13822 -5.00867,-12.65625 -5.46875,-12.65625 -0.76897,0 -0.59705,0.82353 1,4.4375 0.82584,1.86564 1.5,3.92107 1.5,4.5625 0,0.64763 0.51856,2.33843 1.15625,3.75 0.6372,1.41158 1.28414,2.9852 1.4375,3.5 0.15264,0.51789 1.35449,1.26603 2.65625,1.65625 1.69704,0.51068 2.68539,1.26393 3.46876,2.71875 1.05912,1.96552 1.04832,2.14218 -0.0625,5.5625 -0.97129,2.9879 -1.49293,3.67227 -3.37501,4.59375 -5.67145,2.77272 -12.3259,-2.72243 -10.03126,-8.28125 0.4087,-0.99355 0.72936,-1.92797 0.6875,-2.09375 -0.0425,-0.16577 -0.91509,-2.59852 -1.9375,-5.40625 -1.52136,-4.17606 -3.40665,-9.88208 -5.15625,-15.5625 -0.0936,-0.30327 -0.22911,-0.46697 -0.375,-0.5 -0.0365,-0.008 -0.0863,-0.01 -0.125,0 z m 28.78127,24.6875 c 0.0667,0.15373 0.56931,2.1331 1.37501,5.34375 -0.91403,-3.16487 -1.47071,-5.56417 -1.37501,-5.34375 z" + style="fill:#000000;filter:url(#filter4573)" /> + <path + inkscape:connector-curvature="0" + id="path4490" + d="m 188.6005,647.6796 c -13.43643,-2.09817 -14.63585,-2.83972 -20.73411,-12.81917 -2.91656,-4.77278 -5.30283,-9.10387 -5.30283,-9.62466 0,-0.5208 2.04556,-2.83243 4.54569,-5.13697 2.50012,-2.30453 4.54568,-4.44993 4.54568,-4.76755 0,-0.31761 -0.9161,-1.74211 -2.03578,-3.16554 -1.78003,-2.26295 -1.85399,-2.79058 -0.58874,-4.19995 2.37267,-2.64293 9.3291,-17.35827 11.02894,-23.33015 6.14297,-21.58146 10.09563,-59.88779 6.54584,-63.43758 -0.6667,-0.6667 -3.16683,-1.21218 -5.55584,-1.21218 -3.90592,0 -4.34366,-0.24318 -4.34366,-2.41314 0,-1.32723 0.56822,-2.39204 1.2627,-2.36625 3.50263,0.13008 4.79822,-0.43657 4.79822,-2.09861 0,-1.00498 0.6674,-2.70961 1.4831,-3.78807 1.88985,-2.4986 1.89274,-5.64536 0.0167,-18.12328 -1.54971,-10.30725 -3.92248,-16.49104 -9.53346,-24.84559 -3.42892,-5.10553 -2.57454,-7.74124 1.32257,-4.08009 1.79175,1.68325 3.08622,2.0908 5.06415,1.59437 2.54868,-0.63968 3.09046,-1.47956 2.73253,-4.23595 -0.30659,-2.36102 1.72277,-1.2751 2.35971,1.26269 0.84981,3.3859 4.35879,3.3859 5.64611,0 1.15688,-3.04282 2.60208,-3.26247 3.32315,-0.50508 0.73458,2.80903 5.07931,2.65465 7.10726,-0.25254 l 1.58545,-2.27284 0.053,2.27284 c 0.0402,1.72423 0.65044,2.27285 2.52817,2.27285 1.36135,0 2.94508,-0.7955 3.51939,-1.76777 0.95298,-1.61333 1.10003,-1.56921 1.68324,0.50508 0.74042,2.63344 4.89223,3.17395 5.81946,0.75761 0.61174,-1.59417 5.69562,-2.15511 5.69562,-0.62844 0,0.48773 -0.73588,2.64693 -1.63529,4.79822 -0.8994,2.15129 -2.03477,4.96104 -2.52303,6.24387 l -0.88775,2.33244 -4.99731,-2.80913 c -2.74853,-1.54502 -5.35019,-2.45626 -5.78147,-2.02497 -0.43129,0.43129 -0.1677,1.16515 0.58575,1.63081 1.07173,0.66236 0.90046,1.09789 -0.78687,2.00092 -1.18623,0.63485 -2.15678,2.04741 -2.15678,3.13903 0,2.90099 5.82119,2.80881 6.90066,-0.10927 0.75342,-2.03668 0.83386,-2.0436 2.93689,-0.25254 2.07415,1.76647 2.09728,1.99915 0.56765,5.71068 -1.30675,3.17072 -1.97954,3.76326 -3.72705,3.28245 -1.17285,-0.32269 -3.83709,-0.87186 -5.92053,-1.22036 -3.77527,-0.6315 -3.78807,-0.62172 -3.78807,2.89477 0,4.31971 3.147,8.58135 7.7603,10.5089 2.75912,1.15284 3.346,1.91945 3.19293,4.17074 -0.29577,4.35005 1.09164,7.24617 3.79657,7.92506 1.37133,0.34418 3.15429,1.90396 3.96214,3.46617 0.81626,1.57847 2.47895,2.98443 3.74249,3.16463 1.25051,0.17834 2.41309,1.05645 2.5835,1.95136 0.23769,1.24819 -1.06867,1.8983 -5.60983,2.79172 -3.25581,0.64054 -6.45893,1.70389 -7.11804,2.363 -1.64303,1.64303 -1.49165,13.07132 0.27145,20.4923 3.28415,13.8232 16.02038,50.91027 20.14773,58.66888 l 4.35264,8.1821 -1.89003,2.5946 -1.89003,2.5946 5.06574,4.43138 5.06574,4.43139 -3.01125,9.63346 c -3.48707,11.15567 -3.52556,11.1865 -16.87441,13.51435 -10.4107,1.81548 -24.90881,1.77792 -36.90665,-0.0956 l 0,-4e-5 z m 27.04198,-69.56407 c 1.89343,-1.89342 2.47947,-3.54741 2.47947,-6.99785 0,-3.80619 -0.47165,-4.88818 -2.99229,-6.86444 -1.83568,-1.43924 -4.10093,-5.05341 -5.86049,-9.35032 -1.57751,-3.85234 -3.46978,-7.20478 -4.20505,-7.44987 -0.82244,-0.27415 -1.11678,0.0888 -0.76494,0.94334 0.31455,0.76393 1.56741,4.5078 2.78413,8.31972 1.81704,5.69268 2.82013,7.28944 5.61528,8.93862 2.63979,1.55751 3.40305,2.67547 3.40305,4.98444 0,4.045 -4.25071,8.31498 -7.34042,7.3737 -4.34112,-1.3225 -6.02065,-3.66088 -5.40313,-7.52264 0.41787,-2.61325 -0.45141,-6.46962 -3.2531,-14.43155 -3.51698,-9.99468 -5.21655,-12.44991 -5.21655,-7.53594 0,1.06492 1.36239,5.33862 3.02754,9.49713 2.13553,5.33322 2.96508,8.99633 2.81556,12.43275 -0.22383,5.14436 1.09714,7.70929 4.72843,9.1812 3.89802,1.58002 7.64254,1.02169 10.18251,-1.51829 l 0,0 z" + style="fill:#483737;fill-opacity:1;stroke:#241c1c;filter:url(#filter4561)" /> + <path + inkscape:connector-curvature="0" + id="path4492" + d="m 254.09787,646.44221 c -2.95637,-0.30904 -4.12705,-1.04093 -5.30331,-3.31556 -2.21555,-4.28441 -1.94128,-5.52539 2.74475,-12.41863 6.58892,-9.69246 9.17853,-17.65576 9.26197,-28.48139 0.0681,-8.83503 -0.0512,-9.316 -4.23039,-17.05828 -3.8606,-7.15204 -4.19185,-8.29601 -3.23856,-11.1845 0.58404,-1.76967 2.67063,-4.77581 4.63687,-6.6803 3.23868,-3.13699 4.11957,-3.45972 9.36453,-3.43092 9.5091,0.0522 20.37167,7.42773 20.34201,13.81189 -0.0265,5.70983 -0.62823,5.99632 -9.95673,4.7409 -4.58302,-0.61678 -8.57843,-0.87575 -8.87869,-0.57549 -1.07992,1.07992 3.86321,8.30117 7.15732,10.4559 2.62822,1.71915 3.80732,3.59894 5.38577,8.58629 5.07523,16.03591 8.20757,23.11092 12.28057,27.7381 4.36373,4.95746 5.53297,7.68177 4.97009,11.5803 -0.31263,2.1653 -1.29463,2.4888 -13.97332,4.60324 -12.72266,2.12177 -21.41378,2.58485 -30.56288,1.62845 z m 26.52227,-24.5176 c 3.07537,-3.07538 2.46502,-5.89093 -2.27284,-10.48467 -2.36124,-2.2894 -4.80805,-5.07168 -5.43737,-6.18285 -0.62932,-1.11117 -1.45833,-1.72162 -1.84225,-1.35656 -1.1957,1.13696 2.2225,6.50875 6.14302,9.6539 2.60951,2.09342 3.55042,3.51531 3.10106,4.68631 -0.91144,2.37517 -5.13107,4.05887 -8.22568,3.28217 -2.27242,-0.57035 -2.47547,-0.93215 -1.52247,-2.71284 1.45943,-2.72697 0.45826,-6.84476 -2.56324,-10.54255 -2.97769,-3.64416 -3.26524,-1.84082 -0.55392,3.47382 1.68828,3.30929 1.80149,4.34763 0.77154,7.07627 -1.49906,3.97148 -0.16763,5.08929 6.0887,5.11183 2.96138,0.0107 4.91986,-0.61124 6.31345,-2.00483 z m -2.20259,-45.21465 c 0.24255,-1.70007 -0.18208,-2.26252 -1.70815,-2.26252 -2.41781,0 -3.56541,1.52796 -2.74782,3.65856 0.86409,2.25177 4.07925,1.24447 4.45597,-1.39604 l 0,0 z m 2.90387,-3.80967 c -2.41299,-2.12285 -7.77235,-3.51057 -7.77235,-2.01252 0,0.79341 7.45829,4.47954 9.09137,4.49325 0.98418,0.008 0.52189,-0.86117 -1.31902,-2.48073 l 0,0 z" + style="fill:#483737;fill-opacity:1;stroke:#241c1c;filter:url(#filter4549)" /> + <g + transform="matrix(0.99878894,0.04920014,-0.04920014,0.99878894,50.659335,-19.667116)" + id="g3240"> + <g + id="g4070" + transform="matrix(1.25,0,0,-1.25,-132.85714,1032.8764)"> + <path + inkscape:connector-curvature="0" + id="path3027-8" + d="m 440.83954,503.80829 c -0.792,-0.087 -0.86285,-0.62516 -1.17504,-2.1498 -0.0887,-0.43714 -0.62438,-0.64593 -1.42502,-0.55001 -0.70906,0.0847 -1.44231,0.0388 -1.6249,-0.1 -0.18178,-0.13921 -0.15018,-1.88953 0.075,-3.87542 0.37959,-3.34579 0.49917,-3.62089 1.77524,-4.02532 1.3559,-0.42914 1.36454,-0.51272 1.1998,-4.29961 -0.20102,-4.52859 -0.38742,-5.03186 -1.90022,-4.82512 -0.90202,0.12273 -1.36224,-0.24378 -2.19974,-1.77503 -0.32104,-0.58928 -1.41005,-0.81815 -2.35008,-0.97524 -0.20587,0.0378 -0.51756,0.0562 -0.92506,0.05 -1.99814,-0.0302 -6.00157,-0.58953 -7.62503,-1.17539 -0.78278,-0.28116 -1.93306,-0.69594 -2.57472,-0.92499 -0.64166,-0.22899 -1.93939,-0.88464 -2.87482,-1.47521 -1.36224,-0.86157 -1.63584,-1.266 -1.35014,-1.9999 0.19872,-0.50327 0.70618,-2.26924 1.12493,-3.92483 0.41875,-1.6556 1.59897,-5.82507 2.62483,-9.25001 1.02586,-3.42486 2.26195,-8.33177 2.72506,-10.90001 0.46311,-2.56823 0.99417,-4.80535 1.1998,-4.97502 0.20909,-0.16968 0.61114,-1.46204 0.87495,-2.87465 0.47232,-2.53034 0.46514,-2.57565 -0.64973,-3.25024 -0.62035,-0.3769 -1.1641,-0.8871 -1.19981,-1.14986 -0.0359,-0.26299 -0.26562,-0.54322 -0.5,-0.6 -0.81734,-0.19767 -3.62534,-3.95531 -3.7751,-5.04998 -0.11693,-0.85251 -0.45157,-1.0889 -1.42503,-1.02466 -0.69177,0.0454 -1.69401,0.0852 -2.22509,0.10001 -0.5328,0.015 -1.18137,0.0395 -1.44979,0.05 -0.58118,0.0222 -1.38873,-5.40663 -0.90029,-6.04993 0.18524,-0.24381 2.88173,-0.8021 5.97502,-1.22481 3.096,-0.42294 5.7861,-0.97688 5.97502,-1.22481 0.42221,-0.55416 -3.26534,-27.48793 -4.82515,-35.275 -1.52064,-7.59351 -3.06144,-13.03935 -3.82521,-13.52499 -0.34964,-0.21909 -1.21076,-2.7165 -1.925,-5.57467 -0.71596,-2.85652 -2.07648,-6.90903 -3.02515,-9.00002 -0.95097,-2.09133 -1.76774,-4.15464 -1.82477,-4.57472 -0.0576,-0.4209 0.90317,-2.08062 2.15021,-3.67526 1.24704,-1.59547 2.21242,-3.26836 2.15021,-3.72468 -0.15206,-1.11115 -2.01773,-4.69663 -2.57472,-4.95032 -0.64454,-0.29364 -4.77792,-5.96591 -6.39999,-8.77499 l -1.40026,-2.4002 1.57479,-2.79969 c 0.86515,-1.54111 2.00966,-3.02208 2.54995,-3.27495 0.49821,-0.23353 0.99533,-0.6189 1.17504,-0.90029 -0.29697,-0.25545 -0.59386,-0.49437 -0.72519,-0.5 -0.10738,-0.005 -0.21787,-0.0227 -0.32499,-0.05 -0.42797,-0.10931 -1.5938,-0.1947 -2.60007,-0.2 -1.00685,-0.006 -1.96013,-0.17531 -2.12486,-0.375 -0.16105,-0.19933 -0.0694,-2.64483 0.225,-5.42475 0.2984,-2.77992 0.50792,-5.39922 0.45,-5.82507 -0.1044,-0.76362 1.57708,-2.24782 2.24985,-2.25029 0.47537,-0.27389 1.08058,-0.54739 1.49991,-0.7 2.17843,-0.7932 21.14962,-3.33591 24.02501,-3.22471 1.4498,0.0562 4.08269,0.15249 5.82503,0.22499 1.7424,0.0727 4.23015,0.0589 5.54976,-0.025 2.48832,-0.15815 5.77049,0.0654 15.4,1.02465 4.82688,0.48148 5.72256,0.44053 6.10001,-0.2 0.36749,-0.62579 0.57773,-0.56211 1.35014,0.325 0.7344,0.8418 1.04372,2.06332 1.4498,5.95026 0.46352,4.45199 0.40858,4.99233 -0.475,5.80036 -1.58573,1.45297 -4.65235,5.11752 -4.59994,5.49971 0.0265,0.19522 0.77703,1.29153 1.65024,2.44962 1.64794,2.19181 2.61274,2.87712 1.30003,5.37534 -0.14745,0.15321 -1.69171,3.64643 -3.45024,7.75 l -3.20025,7.45019 1.17504,2.57482 c 0.24392,0.53565 0.42363,1.03866 0.575,1.49992 0.134,-0.24349 0.26293,-0.39795 0.325,-0.275 0.0416,0.0836 -0.004,0.56253 -0.075,1.14986 0.0812,0.40157 0.0962,0.74015 0.025,0.90028 -0.0686,0.15435 -0.12672,0.23117 -0.2,0.25 -0.21443,1.39285 -0.49675,2.95043 -0.67508,3.3252 -0.31968,0.67789 -0.83692,2.66708 -1.15027,4.42481 -0.31161,1.75773 -0.9815,4.91984 -1.47513,7.04988 -2.35124,10.10087 -2.70029,11.86659 -3.02516,14.84996 -0.19521,1.75938 -0.5217,3.43392 -0.69984,3.72468 -0.18101,0.29159 -0.4934,1.75608 -0.69984,3.25024 -0.20851,1.49333 -0.62092,4.26831 -0.92505,6.17513 -0.30344,1.90682 -0.65031,5.76082 -0.77472,8.575 -0.12676,2.81534 -0.3458,5.68751 -0.5,6.37529 -0.6002,2.68519 -0.51321,11.41867 0.125,11.75004 0.76954,0.40109 8.78348,0.52009 9.99999,0.15 l 1.47514,-0.45 0.60019,3.14975 c 0.32601,1.72726 0.50603,3.31366 0.4,3.55006 -0.10713,0.23685 -1.23264,0.64304 -2.49984,0.90028 -1.60646,0.32536 -2.31494,0.69273 -2.32473,1.2001 -0.008,0.40114 -0.0607,1.06996 -0.1,1.47521 -0.0392,0.40524 0.20693,0.63511 0.52499,0.52501 0.83866,-0.28994 0.864,0.98265 0.05,1.8747 -0.38477,0.41843 -1.71706,1.37637 -2.97504,2.12509 -1.1641,0.69248 -2.42784,1.29318 -2.97504,1.44968 0.46396,2.02872 0.56734,6.60014 2.1502,10.20004 0.66528,1.50733 1.39392,2.93971 1.6249,3.17528 0.2281,0.23559 0.68026,1.25282 0.99994,2.27501 0.31968,1.02219 1.18771,3.1028 1.92499,4.60025 0.73958,1.49828 2.0471,4.67768 2.90016,7.07541 1.79366,5.04257 1.8288,5.1167 3.04992,4.95032 1.43251,-0.19522 1.18598,0.43864 -0.82483,2.10038 -1.27412,1.05184 -3.28493,1.98013 -6.425,2.9496 -2.5102,0.7746 -4.7255,1.4192 -4.95014,1.44968 -0.22808,0.0313 -0.57306,0.84262 -0.74995,1.79974 -0.17741,0.95712 -0.62784,1.77421 -0.99994,1.82527 -0.11794,0.0162 -0.2125,0.0301 -0.3,0.025 -0.12696,-0.007 -0.2128,-0.0627 -0.27499,-0.125 -0.77415,0.0286 -2.05229,-0.0392 -3.02516,0 l -2.40019,0.1 -0.25,1.77503 c -0.13302,0.97771 -0.19521,2.05756 -0.15,2.40021 0.0214,0.16152 -0.027,0.27484 -0.1,0.35 -0.005,0.005 0.005,0.0208 0,0.025 0.12678,0.66789 0.44008,2.2717 0.575,3.17528 0.96077,-0.633 2.42899,-1.3566 2.72506,-1.22481 0.37068,0.16556 0.97632,6.06393 0.74995,7.30028 -0.0576,0.3412 -0.7511,0.69076 -1.55002,0.79999 -1.53561,0.21005 -1.56844,0.25858 -1.94976,3.55007 l -0.25,2.07485 -3.04992,0.425 c -1.20672,0.16494 -1.94976,0.27721 -2.42496,0.22499 z m 1.15027,-1.12515 c 0.38068,-5.4e-4 0.84499,-0.058 1.40026,-0.175 2.01715,-0.42584 2.08051,-0.47528 2.40019,-2.77497 0.21715,-1.5617 0.59443,-2.41503 1.12493,-2.52541 0.43546,-0.0903 1.09901,-0.21271 1.47513,-0.3 0.80813,-0.1878 1.13933,-3.0995 0.55,-4.79958 -0.34905,-1.01807 -0.61977,-1.11609 -1.8,-0.775 -0.61517,0.17774 -1.13645,0.24225 -1.3248,0.175 -0.002,-4.1e-4 -0.024,8e-4 -0.025,0 -0.002,-0.002 0.002,-0.0221 0,-0.025 -7.6e-4,-8e-4 -0.0243,8e-4 -0.025,0 -0.0204,-0.0136 -0.0224,-0.0304 -0.025,-0.05 -0.002,-0.0123 -0.004,-0.0354 0,-0.05 -0.17477,-0.58538 -0.40485,-3.76752 -0.35,-4.32515 -0.1689,-0.42408 -0.32203,-1.08149 -0.375,-1.79974 l -0.15,-1.99989 -2.2752,0.225 c -2.15885,0.21251 -2.26771,0.28507 -2.12486,1.49992 0.0825,0.70261 0.15677,2.16134 0.175,3.22471 0.0182,1.06337 0.0883,2.67614 0.15,3.60031 0.0939,1.45214 -0.0653,1.73384 -1.19981,2.0246 -1.40947,0.36159 -2.18189,2.16793 -2.12486,4.97503 0.0248,1.32942 0.12093,1.42908 1.30003,1.12514 1.07366,-0.27644 1.31731,-0.14752 1.44979,0.77501 0.20218,1.41425 0.63302,1.97683 1.77523,1.97518 z m -4.22496,-19.475 c 0.44242,0.0598 3.3696,-0.28393 6.49999,-0.775 4.26585,-0.66927 6.62336,-0.64939 7.67503,-0.30001 0.11435,-0.38867 0.33867,-0.89781 0.72518,-1.67536 0.9481,-1.91011 2.05344,-2.49657 7.95001,-4.22466 3.10752,-0.91099 4.66906,-2.11768 3.97498,-3.07479 -0.30183,-0.42337 -0.95962,-2.06991 -1.47514,-3.64973 -0.5184,-1.57982 -2.90189,-7.1413 -5.29978,-12.35001 -4.67539,-10.16438 -6.51058,-16.21653 -5.90002,-16.29997 0.055,-0.007 0.48287,-0.44077 0.90029,-0.74999 0.004,-0.002 -0.004,-0.0224 0,-0.025 0.19976,-0.50933 1.00051,-1.3113 2.24985,-2.1498 2.33914,-1.57158 2.37024,-1.64736 2.32474,-4.1497 -0.0547,-3.03938 0.30585,-3.67938 1.8,-3.12504 0.60653,0.22467 1.64333,0.27281 2.29997,0.1 1.08518,-0.28417 1.17792,-0.54686 0.97516,-2.62507 l -0.22499,-2.275 -1.69978,0.52499 c -1.05926,0.33133 -2.35872,0.36432 -3.45024,0.075 -0.9648,-0.25552 -2.62598,-0.49104 -3.70022,-0.52501 -2.78324,-0.0865 -3.18068,-0.94888 -3.27514,-7.12483 -0.0402,-2.80298 0.0715,-6.41317 0.25,-8.02511 0.17485,-1.61277 0.33348,-3.89931 0.35,-5.10023 0.034,-2.60118 1.97165,-15.33 2.44973,-16.12502 0.1849,-0.30641 0.7655,-2.84087 1.30003,-5.64962 0.53453,-2.80875 1.09037,-5.24767 1.22515,-5.42476 0.13478,-0.17791 0.18233,-0.82293 0.1,-1.42496 -0.0824,-0.60211 0.43715,-3.64314 1.17504,-6.75006 0.73786,-3.10692 1.5529,-6.7583 1.8,-8.12478 0.35914,-1.98589 1.37895,-5.80694 2.12487,-7.92463 -0.0152,-0.041 -0.0343,-0.0559 -0.05,-0.1 -0.26727,-0.76108 -1.08807,-2.24782 -1.82477,-3.29966 l -1.3248,-1.92494 1.35014,-2.20005 c 0.74707,-1.21493 1.78503,-3.36803 2.32474,-4.77487 0.97056,-2.50893 2.18361,-5.28885 3.74976,-8.675 0.86227,-1.8574 0.90547,-1.69678 -2.22509,-5.80036 l -1.05005,-1.40025 1.42503,-2.29972 c 0.78508,-1.27423 2.15308,-2.90264 3.07526,-3.6003 1.62778,-1.2314 1.67904,-1.3286 1.24992,-3.69997 -0.24283,-1.34013 -0.59328,-3.34826 -0.77472,-4.45035 -0.2759,-1.67783 -0.50455,-2.01719 -1.42502,-2.07484 -0.6048,-0.0379 -3.53549,-0.27224 -6.49999,-0.525 -8.54571,-0.72853 -12.52495,-0.94971 -15.375,-0.87475 -1.46016,0.0384 -4.94784,0.0438 -7.75002,0 -5.50138,-0.0857 -24.9844,2.22805 -27.35003,3.25024 -0.64628,0.2785 -1.29658,0.39757 -1.65024,0.325 -0.18168,0.20364 -0.35556,0.41739 -0.42501,0.6 -0.35942,0.94394 -1.20672,9.8354 -1.02528,10.72497 0.053,0.25923 0.34784,0.47148 0.67508,0.45001 0.32832,-0.0217 1.4158,0.006 2.42496,0.075 1.19692,0.0814 2.18592,0.89699 2.54995,1.64983 0.26938,0.1183 0.50825,0.34465 0.55,0.65 0.0335,0.24612 -0.2121,0.4788 -0.55,0.52501 -0.67335,0.0914 -3.72039,4.11016 -4.27507,5.62491 -0.35943,0.98676 -0.002,1.69678 2.67494,5.49971 5.97894,8.48077 7.09592,10.23109 7.225,11.17503 0.0996,0.72731 -0.68026,2.01637 -2.37485,3.95037 -2.40653,2.74615 -2.86733,3.96025 -1.82477,4.82512 0.65607,0.54642 3.25325,6.37528 3.84999,8.65004 0.27417,1.02795 0.88646,2.88946 1.35014,4.12498 3.2567,8.68406 4.50893,14.39002 7.15,32.02501 2.91514,19.45351 3.0649,22.37066 1.12493,23.15002 -0.59789,0.23984 -3.31315,0.67024 -6.02502,0.94971 l -4.9248,0.49999 0.10001,1.90023 c 0.11232,2.0139 1.26374,3.19094 2.47507,2.52541 0.34099,-0.18582 1.40832,-0.20455 2.37485,-0.025 1.50912,0.27839 1.73491,0.50581 1.62489,1.49992 -0.22176,1.99331 3.59482,5.9725 5.50023,5.75011 0.65491,-0.0774 0.76435,0.24561 0.67507,2.07485 -0.0587,1.18775 -0.32949,2.98255 -0.60019,3.99979 -0.576,2.16545 -0.28239,2.69838 1.35014,2.47516 2.55399,-0.34924 6.4406,2.93724 7.55004,6.37528 0.77817,2.41833 0.50046,3.43063 -1.24992,4.57472 -2.19975,1.43815 -5.07226,1.67289 -7.75002,0.625 -1.2649,-0.49291 -2.39501,-0.86816 -2.52519,-0.85004 -0.2183,0.0298 -4.35341,12.62257 -5.22489,15.89999 -0.21032,0.79157 -0.2447,1.76597 -0.05,2.17534 0.55872,1.17457 6.02231,3.3532 9.14999,3.64973 1.31444,0.12469 3.21869,0.30304 3.9001,0.475 1.08749,-0.24984 3.7993,0.39167 3.97498,1.17539 0.44352,1.96859 0.864,1.52957 1.57478,1.62512 z m -6.82502,-9.69999 c -1.17792,-0.0348 -2.50963,-0.31913 -3.67488,-0.85004 -2.35181,-1.06996 -3.46637,-2.06249 -3.15015,-2.77497 0.35079,-0.79014 1.14048,-0.75183 1.24992,0.05 0.14279,1.04525 4.29293,2.56741 6.25001,2.29972 1.60934,-0.22074 3.05856,-1.39944 2.92492,-2.37467 -0.0328,-0.24288 0.28265,-0.47135 0.67508,-0.525 0.39079,-0.0534 0.70675,0.0526 0.72518,0.24999 0.0478,0.56094 -1.24186,2.47516 -2.19974,3.25025 -0.59328,0.47905 -1.62202,0.70978 -2.79994,0.67499 z m -1.69978,-5.475 c -0.9216,0 -1.04601,-0.28167 -1.24992,-1.77503 -0.18374,-1.34425 -0.009,-2.06908 0.64973,-2.64978 0.79949,-0.70345 1.06157,-0.69907 2.29997,0.025 1.60934,0.94476 2.0016,2.4521 0.92505,3.55006 -0.4078,0.41679 -1.38412,0.78222 -2.17497,0.82533 -0.16681,0.009 -0.31838,0.025 -0.45,0.025 z m 0.64973,-1.12515 c 0.54913,0.16086 1.4377,-1.25281 1.17504,-1.99989 -0.32429,-0.92582 -1.68595,-0.70736 -1.87488,0.29999 -0.0658,0.3504 0.0597,0.56934 0.275,0.50001 0.93139,-0.30031 1.28909,-0.0959 0.69984,0.39999 -0.3515,0.2959 -0.51552,0.64329 -0.375,0.75 0.0333,0.0255 0.0634,0.0393 0.1,0.05 z m -0.375,-9.07497 c 2.1145,-0.0194 4.32922,-0.5873 4.65005,-1.44968 0.77299,-2.08391 0.0562,-3.67938 -2.44973,-5.42476 -4.53658,-3.16375 -6.29988,-2.75191 -6.20001,1.47521 0.0274,1.22811 -0.11732,2.37879 -0.325,2.55012 -0.76781,0.63341 -0.14564,1.71408 1.37491,2.37467 0.55264,0.24011 1.27181,0.39556 2.04999,0.45 0.29191,0.0204 0.59789,0.0278 0.90029,0.025 z m 7.2,-48.50001 c -1.00282,-0.25581 -2.79418,-7.0194 -4.09997,-15.84999 -0.39458,-2.6679 -2.03731,-5.43712 -2.54995,-6.84973 -0.47141,0.40941 -4.50259,-2.62259 -5.2249,-3.95037 -0.4896,-0.89534 -0.35054,-2.36725 -0.1,-4.99973 0.29607,-3.14811 0.5289,-3.72139 1.99988,-5.12494 3.4462,-3.29307 7.01838,-3.10527 11.27502,0.6 2.28154,1.98589 2.36564,2.11851 2.65018,5.02527 0.16105,1.64572 0.0639,3.77905 -0.2,4.72463 -0.83635,2.99737 -0.94521,5.90332 -0.275,7.42465 0.3456,0.78332 0.80237,2.60942 0.99994,4.05004 0.34675,2.49245 2.87654,10.52053 3.84998,12.20002 l -0.025,0 c 0.24829,0.42908 0.29251,0.62471 0.075,0.64999 -0.0724,0.008 -0.17322,0.005 -0.29999,-0.025 -1.24301,-0.29183 -3.45197,-6.51202 -5.25024,-14.77501 -1.91232,-8.79937 -1.92672,-8.67953 -0.64973,-9.37504 1.21536,-0.66142 1.27065,-1.09303 0.5,-5.37534 -0.35654,-2.01307 -0.79027,-2.89524 -1.92499,-3.92484 -1.95091,-1.77503 -3.30739,-2.15557 -6.275,-1.75032 -2.96756,0.40607 -4.22554,1.4085 -4.95015,3.97508 -1.3392,4.74852 -0.60998,7.23521 2.67495,9.15002 0.48783,0.28436 0.8064,0.62952 0.99993,0.92499 0.0199,-0.50426 1.56039,0.15372 1.67501,0.175 0.49536,0.0918 2.6784,8.33465 4.0752,17.15001 0.35078,2.21652 0.88301,4.2436 1.17504,4.52529 0.29197,0.28088 0.3976,0.79248 0.25,1.12515 -0.10721,0.24178 -0.23177,0.33654 -0.375,0.29999 z m 22.05003,-59.97503 c 0.26154,-0.0357 0.43576,-0.28842 0.4,-0.54999 -0.0358,-0.26156 -0.26345,-0.43577 -0.525,-0.40001 -0.26155,0.0358 -0.46076,0.26346 -0.425,0.52501 0.0358,0.26158 0.28844,0.46075 0.55,0.42499 z" + style="fill:#000000" /> + <path + inkscape:connector-curvature="0" + id="path3092" + d="m 426.32893,449.41179 c -0.46598,0.46598 -0.72691,1.64159 -0.72806,3.28236 -6.6e-4,0.85498 -0.1294,1.77833 -0.30746,2.20417 -0.39427,0.94394 -0.2418,1.38049 0.68659,1.96612 1.51949,0.95877 3.87475,1.10621 6.42499,0.40183 1.35015,-0.37297 1.88295,-1.02136 1.87891,-2.28324 -0.005,-1.25529 -0.63763,-2.31537 -2.07532,-3.47346 -2.43764,-1.96365 -5.07341,-2.9043 -5.87929,-2.09874 z" + style="fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:0.80812281;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <path + style="fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:1.01015353;stroke-miterlimit:4;stroke-dasharray:none" + d="m 404.08148,451.81696 c -0.5595,-0.61822 -0.34155,-0.99442 0.48465,-0.83648 0.73151,0.13991 0.88631,-0.42614 0.25856,-0.9472 -0.20835,-0.17292 -0.37881,-0.43392 -0.37881,-0.58002 0,-0.47248 1.21464,0.86004 1.3788,1.51249 0.30384,1.2108 -0.88992,1.79356 -1.74312,0.85168 z" + id="path3094" + inkscape:connector-curvature="0" /> + <path + style="fill:#e6e6e6;fill-opacity:1;stroke:none" + d="m 400.34386,645.58253 c -2.75,-0.28097 -9.26814,-1.18845 -14.48477,-2.01664 -8.98763,-1.42684 -9.53049,-1.7344 -10.35714,-5.86766 -1.50156,-7.50781 -1.06919,-9.71901 1.90046,-9.71901 4.2941,0 4.83562,-2.4765 1.55035,-7.09023 l -3.00034,-4.21359 6.64279,-9.98982 6.64278,-9.98983 -3.07647,-4.97785 -3.07648,-4.97784 3.45922,-8.30898 c 6.89103,-16.55217 13.49708,-48.36714 14.17478,-68.26617 l 0.33911,-9.95716 -7.41834,-1.21023 c -5.56059,-0.90716 -7.22164,-1.72283 -6.63288,-3.25712 0.43201,-1.12579 2.32384,-2.0469 4.20406,-2.0469 2.00911,0 3.41859,-0.84074 3.41859,-2.03917 0,-1.12154 1.60714,-3.30335 3.57142,-4.84845 2.62991,-2.06869 3.57143,-4.16738 3.57143,-7.96083 0,-4.1345 0.5436,-5.15155 2.75346,-5.15155 3.22556,0 8.67511,-6.37127 8.67511,-10.14239 0,-3.50223 -4.97784,-5.97046 -10.69497,-5.30301 -4.60437,0.53753 -4.68961,0.43724 -7.77754,-9.15046 -1.71718,-5.33167 -2.75439,-10.28899 -2.30492,-11.01625 0.94822,-1.53424 7.74192,-3.75762 15.42029,-5.04661 2.94643,-0.49462 5.35714,-1.50318 5.35714,-2.24125 0,-1.9835 17.58836,0.16433 19.28572,2.3551 0.78571,1.01413 4.4698,3.1306 8.18686,4.70327 l 6.75829,2.85942 -5.1031,11.84823 c -7.89709,18.33524 -10.55634,25.32987 -10.55634,27.76633 0,1.23249 1.60715,4.15087 3.57143,6.48529 1.96429,2.33443 3.57143,5.15937 3.57143,6.27766 0,1.11828 1.28571,2.03325 2.85714,2.03325 1.5873,0 2.85715,0.95238 2.85715,2.14285 0,1.5848 -1.66109,2.14286 -6.37831,2.14286 -4.84397,0 -6.73405,0.66471 -7.85715,2.76323 -3.17543,5.93335 -1.29958,24.0451 6.50874,62.84321 4.09103,20.32756 4.71571,25.60186 3.40658,28.76236 -1.37961,3.33069 -0.99108,5.08257 2.7871,12.56711 4.88516,9.67746 5.26479,12.5023 2.22083,16.5255 -1.86887,2.4701 -1.71126,3.22859 1.56293,7.52128 2.552,3.34584 3.50796,6.1568 3.19534,9.39572 -0.44228,4.58236 -0.52488,4.62926 -9.73178,5.52605 -9.91327,0.96557 -31.75079,1.11313 -40,0.27028 l 0,0 z m 14.58317,-79.10955 c 4.09153,-4.09154 4.30748,-4.83898 3.46619,-11.99708 -0.68245,-5.80657 -0.0911,-10.4166 2.47047,-19.26072 2.01547,-6.95848 2.73061,-11.41954 1.78106,-11.11027 -2.91204,0.94847 -6.87351,15.83246 -7.18045,26.97833 -0.33516,12.1705 -2.01198,15.46741 -7.86679,15.46741 -7.91857,0 -10.1324,-8.95678 -3.50277,-14.17164 2.90824,-2.28763 4.36222,-5.60044 6.47219,-14.74655 1.49672,-6.48785 3.06643,-12.9211 3.48824,-14.2961 0.42182,-1.375 0.13062,-2.5 -0.64711,-2.5 -1.64375,0 -3.63148,5.87974 -5.94898,17.5972 -1.21277,6.13187 -2.7359,9.2845 -5.80811,12.02186 -7.72817,6.88583 -4.0859,20.38094 5.50073,20.38094 1.89978,0 5.34578,-1.93383 7.77533,-4.36338 l 0,0 z m -7.56592,-113.34241 c 1.27646,-1.53805 1.12743,-2.51688 -0.65454,-4.29884 -2.66481,-2.66482 -4.93414,-1.81356 -4.93414,1.85085 0,4.54198 2.82416,5.77903 5.58868,2.44799 z m -6.30296,-8.00849 c 0.4856,-0.78572 2.45211,-1.42858 4.37002,-1.42858 1.91792,0 3.48712,0.64286 3.48712,1.42858 0,0.78571 0.9869,1.42857 2.19312,1.42857 1.75044,0 1.88447,-0.57672 0.66402,-2.85715 -1.89102,-3.5334 -10.17182,-3.9098 -13.46938,-0.61224 -1.2347,1.23469 -2.2449,2.52041 -2.2449,2.85714 0,1.24187 4.14635,0.56491 5,-0.81632 z" + id="path3236" + inkscape:connector-curvature="0" /> + <path + style="fill:#e6e6e6;fill-opacity:1;stroke:none" + d="m 420.09725,428.14616 c -1.95597,-0.29631 -1.96428,-0.32194 -1.96428,-6.06107 0,-5.23023 -0.16523,-5.91663 -1.78572,-7.41845 -2.3226,-2.15252 -2.61383,-6.80597 -0.38953,-6.22431 1.05492,0.27587 1.51032,-0.0897 1.86315,-1.49544 0.36247,-1.4442 0.90959,-1.86055 2.44493,-1.86055 2.24315,0 3.17557,0.91175 3.85981,3.77429 0.27306,1.14235 1.24162,2.33772 2.27605,2.80903 1.55826,0.70999 1.77199,1.21161 1.58743,3.72571 -0.19822,2.70034 -0.35181,2.89075 -2.17755,2.69965 -1.73832,-0.18196 -1.96429,0.0325 -1.96429,1.86459 0,3.22425 -0.86015,8.70994 -1.34708,8.59113 -0.24125,-0.0589 -1.32256,-0.24092 -2.40292,-0.40458 z" + id="path3238" + inkscape:connector-curvature="0" /> + </g> + </g> + <g + id="g3325" + transform="translate(-5.7142855,-17.142862)"> + <text + sodipodi:linespacing="125%" + id="text3280" + y="982.56226" + x="166.98186" + style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Monospace;-inkscape-font-specification:Monospace" + xml:space="preserve"><tspan + y="982.56226" + x="166.98186" + id="tspan3282" + sodipodi:role="line">UCC::Progcomp 2013</tspan></text> + <text + sodipodi:linespacing="125%" + id="text3284" + y="1022.0576" + x="173.74623" + style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Monospace;-inkscape-font-specification:Monospace" + xml:space="preserve"><tspan + style="font-size:20px" + y="1022.0576" + x="173.74623" + id="tspan3286" + sodipodi:role="line">http://progcomp.ucc.asn.au/2013/web</tspan></text> + </g> + <text + xml:space="preserve" + style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Monospace;-inkscape-font-specification:Monospace" + x="228.57143" + y="135.21933" + id="text3331" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3333" + x="228.57143" + y="135.21933" /></text> + <image + y="108.98257" + x="172.44629" + id="image3446" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgEAAAH9CAIAAAAMPDjJAAAAA3NCSVQICAjb4U/gAAAgAElEQVR4 +nOzdeXwTZf448CdXkzZt0ittelBKD6CUUq4WkFMF/HKI31VXYJX157ELqwvsKiKKKApCuXXBVaR8 +EQHhqyuiIqCClBtLaaEtvUuatE3apEmvNMckk/n9Mbv51jZN02aSyfF5/+FrMjOZz8cwzSfzzDzP +w7h58ybyZuvWraM7Bafk5OTQnYJT4POnl7d//kdSUuhOwSnLamroTsEp+/+5j0l3DgAAAGjDdt2h +tVptcHCw647vChiGqdXq9vZ2rVZrNBrNZjOTyeRwOCEhIREREVFRUUymR1dNjUZz5cqV4uLiqqqq +lpYWrVbL4XBCQ0PT0tKmTZs2e/ZsDodDd46OIgiis7MTISQQCOjOBQCf5aoaUFNT8+c//3n16tWP +PfaYi0JQq729XSaTqdXqHutxHMdx3GAwqFQqiUQycuTIsLAwWjK07+7du0ePHr127ZrFYum+3mQy +6XQ6uVx+4cKF/fv3b9iwYcKECXQlOSBms7mwsJDJZM6YMYPuXADwWZT9qpXJZO3t7daXlZWVOp1u ++/btNV7SXlZUVNS7APRgNBqLi4tbWlrck9KALF++/MqVKz0KQA9NTU2rVq26dOmS27IaHAzDJBKJ +VCpFCBEEIZFIJBIJjuN05wWAD6LsOuDzzz/Py8vbuHHjtGnTEELTpk0LCAjAMOzcuXN//etfqYri +BlwuNzo6OiIiIigoiMViGY1GtVotlUpNJhNCiCCI8vLy7OxsLpdLd6a2icXiuXPnTp8+fciQIXw+ +v7m5+erVq4cPH25tbUUI4Tj+7rvvnjhxIioqiu5M+2Q2m1UqFUEQCCGCIFQqFUJoyJAhdOcFgA+i +7DpALpdrtdr169fX1tYihIRCIZ/PRwiRTbpegcPhpKamTpo0KSkpSSgUcjgcJpMZGBgYHx8/ceJE +Ho9H7objeENDA72p2hQWFvbaa6999dVXL730UkZGRmhoKIfDiY+PX7JkyeHDh2NiYsjddDrdiRMn +6E3VvqCgoOzs7HHjxiGEmExmVlZWdnY2m+3Ce1cA+C3KasDChQsRQkajcfPmzbm5uRs2bCB/eJKX +BZ5PJBJlZ2fHxcXZvOvL5XKTk5OtL/ttNXK/hx9++Pjx40888YTNu75RUVErV660vrxx44YbUxuM +zs7OgoICHo/HZrNv3bplMBjozggA30RZDZgzZw75S7m8vDw3N/fnn38m17/99ts//PADVVFcJz09 +3f4zM91vBRuNRtdnNDDvv/9+aGionR2ys7Oty01NTa7PaGAsFoterzcYDOQtjaqqqqioqMmTJ0+Z +MoXP55MXl+Q+er2ebCYCADiPsuvrHTt22PyxNnny5B07dpSWlq5du5bBYFAVjl7e/j/iOe0qBEEo +lUq5XN7R0UF+szOZzNDQUK1WO2rUKIQQg8GIioqqrKwsKirqvk9YWFhcXFx4eDjN/wMAeDlqrgO+ +/vrr7777DiEkEokCAgK6b5JKpbm5uXfu3Nm6datX/3wjm7ZI5K0O73Lr1i3r8rBhw2jMxMpoNN69 +e7e8vJx8oozH43G5XIvFotFoCIKwPn/V2tpqNput+wQEBFgsFrVaXVxcXFZWZjab6fx/AMDLUfB7 +sKWlZd++fSKRaPv27RiG/eUvf+m+9f79+zt37ty6detbb721Y8eOtWvXOh/R/SwWi0Qisb6Mjo6m +MZlBwDDs008/tb6cP38+jcmQMAy7ffs2hmEBAQFDhw6Njo4me+QxmcympiapVFpbW6tWqy0WS0dH +R2BgYEJCQlRUFPmNz2AwyH2USqVOpxs/fryH990DwGNR8Jfz6aef6vX6pUuXpqWl/fDDD70fUb9z +587rr7++ZcuW/Pz8c+fOOR/RzcjnQXU6HfmSz+eLxWJ6UxoQHMc3btxorWHJyckLFiygNyWCIMrK +yjAMEwgEEyZMwDDs119/vXnz5vXr1wsLCzkcTlZWVnBwcFtbW0dHR1hY2MSJEy0Wy61bt27cuHHj +xo3bt28zmcwJEyYEBgZqtdrq6mp6/3cA8F4U1ICKigqE0Llz5954440zZ87Y3Ecqlebk5GzcuHHP +nj319fXOB3UbgiAqKirIR9QRQmw2Oz093Yt+deI4/t577/3yyy/kS4FAsGXLFtpHjGhpaWlra+Nw +OKNGjaqoqLB2v0AI6XS68vJylUplLbRxcXF1dXXV1dXWG05Go7GmpkYmk5H/FgqFQqvV0vN/AoCX +o+C7jPzrraqqunjxop3G2cLCwqtXrz777LNbt251Pqh7kAWgubmZfMliscaMGRMUFERvVo4jC8CP +P/5IvgwKCtq9e/fQoUPpzQohpFAoEELx8fEqlar7jRarrq6u7i+tF2HdNTU1GQwGsrObBz7pBIBX +oKAGOP4T7NixY5MnT1apVFevXnU+rquR7RXWAsBmszMzM71o/DKz2bxhwwZrARAIBHv37h09ejS9 +WZHIG7xRUVFkMRg0hUJB1oC2tjZqMgPAzzhbAzAM02g0Du5sMpkOHDiwYsWKf/7zn54//Et1dbW1 +CYjD4YwdO9aLCgBCaNeuXdYmIKFQ+M9//jM9PZ3elEhmsxnHcQaDweVy9Xq9M4fq6uoKDAxEHtlj +AwCv4GwNkMlkA/o2v3z5clpamsViuXbtmpOhXUqj0cjlcnKZyWRmZGR41zjY+fn533zzDbnM5XL3 +7NmT4jGTdZC9K8gHhZ3sacFkMslnELzoDg0AHsXZv5zi4mI7W1ksVo/bjziOnzx5csGCBWR/Ao/V +vX05ISHBu64AEEKnT5+2Lj/77LNkfysPwWKxyE4kXV1dQqHQmUMJhULyzgF5NQAAGChna8Dt27ft +bJ0+fXrvq4RLly7NmzcvPz/f2tLigTo6OqzLXtcbACFUUlJiXZ47dy6NmdhE9u9VKBQJCQmDPgiT +yRwyZAh5R8EzJ3UAwPM5VQPIx7r5fP6LL75IjvLYnUgkSkhI6N1doL6+vrOzMyMj4/z5885EdykM +w6zLHjtMtB3db9J4YG+G2NhYhJBCoWAymampqYNoEWKxWGlpaVqttrW1lclkWkdFBQAMiFP9hG/c +uKHVajdv3vzQQw/dvXvXuj46Onrs2LGzZs3atGmTzTfeunVr4sSJ+fn5S5cudSYB1+leuryxrbn7 +PVLPGR3ISiAQiMXipqam0tLStLS0CRMm9LgoDAkJ6T78VFRUVPf7MQwGIzo6ur29vaqqCiGUmJjY +Y4QSAICDnPp2I78cc3Nzly5deuvWLfL3cnJy8ldffbV48eKcnBybj3UjhCoqKrKysoqKiqw9g4C/ +SUlJCQwMNJlMxcXFdXV1jN/SarXWSxmlUqnX67tvRQhVVlZWVFRYLBahUAjTywAwaE79Qpw+ffrq +1av379/PZrNff/31Dz74QCwWb9++/cyZM7t37+7enNJDRUVFWloaQqi4uNhbprcF1GKz2WPHji0u +Lu7q6mppabEzQ6dSqexrk1AozMjI8PZhXAGgkbOtBEuXLl20aJFery8vL1+6dOmTTz554MCBb7/9 +1v67yKlihw4dWlFR4Zk1YNasWXSn4JSbN2/SnUL/uFzuxIkTVSpVWVmZQCBw/L6uSqUyGAyjR4+G +saMBcBIFLcV8Pp/P50+fPn3y5Ml/+9vf7D8pRMJxXK1WJyUl3b9/3/kEgPdiMBgREREIIaFQGBkZ +aXPciB4iIyN1Oh2GYVAAAHAelXcLc3JyHCkAJKVSmZSUZO3ICkB7e7sjvwm8aLwmADwfZTWgpKRk +QHNGqlSqmJgYiURCEIQHtufm5eVZl72xXWjy5MnWZa9oFwIA0IKypx77vQfQg16vDw8PNxgM3Xtj +AQAAcCfKrgPq6uoGtL/BYCDvAba2tjo5YADwDUKh0JFBjbxxIk8APBZlNaB3f2D79Ho9+dXf2tqa +mJhIVRrAS7W2tjo43XRjYyPMGAMAVSirAXFxcWVlZY7vv3fv3r1796LfztUO/JZWqx3QN7sHdn4G +wBtR9oe0adOmvkaG8EbeeB+4Oy+6D8xisbz90wbAe3nfSDgAAACoAjUAAAD8F9QAAADwXz3vB3zy +ySfkwrJly/h8/pEjR8h5mmbPnp2SknL+/PmamhqE0Pjx47Ozs12Uk0Qi4XA48fHxzh+qq6uLyWS6 +eZIpo9HY1NQUFBQkEomcPFRLS8vp06eHDh364IMPUpKb+4PW19eHhoaGhIQ4fygPDwqAN+pZAz77 +7DNy4fHHH+fz+cePHyeH8E1OTk5JScnLy7NO/EJ5DSgpKUlLS2Oz2SdPnrx169axY8dYLNbgDtXe +3i4QCBgMhlwub21tzcrKcltXZIvFUlRURI59P3z4cHKylMHBMGz58uWNjY0IobVr1z7++OOUZemW +oGazub6+Xi6Xt7W1icVi5yuixwYFwHv1bAt64403yLlnt2zZsnbtWuvjel988cXatWvJiWKWL1/u +igc5yIgIoREjRtTV1VmndB+EyspKcjLF4OBgnU6n1+spy7I/RqPROvlJW1ubM4dqaWkhv4sRQt2n +6HEpCoMymcyQkBAGg8Hj8Xg8HhXZeWhQALxXz+uAxx577NatW2VlZfPmzWMymXPmzOm+lcPhXLhw +4cEHH3RFr66ZM2d+9tlnL730kkKh4HK5nZ2dgz5UZGSkTCa7c+eOwWBgMplms5nCPO0LDAwMDQ0l +v/2dnOAwNjZ2woQJ5DB8CxYsoCY/NwZlMpltbW1RUVE6nc5tc7HREhQA72Wjf0BWVtb58+crKip6 +t5+oVKr4+HhXzN1qNptLS0sRQoWFhQghFou1b9++hQsXzp8/f6CHIgiCHIOI/CJmMBj3798Xi8Vu +mFaXIIjq6mrrz/+Kior09HSBQDCIQ+E4vmvXLvLTQAi9//7777///ujRoynL1TVBDQZDQ0NDW1ub +2WwODg4mCCIjI6Ojo6OystJisZjN5sDAQLFYHB0dTWHatAQFwDfYqAGLFi06ffr08ePHbb5h9+7d +rphj/dChQ+3t7U888cTXX3+NEMJxXCgUVlVVMRiMefPmDehQdXV1ZrM5NjaWbE0iCILD4Wi12qam +JleXAZlMRgYdN26cQqFoamoqKSnJzs7mcDgDPdSRI0dOnjyJEDpw4MCpU6d++OGHNWvW/O///q9L +x1ZyMmhbW1tpaWlwcHBMTAyTydRqtc3NzVKptK2tDcfxmJgYFoul1+urq6tVKlV6ejolN2loCQqA +z7BxscxgMDIyMvp6A3m3gFo6ne7o0aOLFi2qr6+3rrx48aJUKi0pKSHnDXcQjuP19fUxMTHd7wGo +VCqdTtfR0eHqcWYUCkV0dLRYLA4ODg4PDxeLxQEBAT1mS3fQt99++8gjjyxYsCA1NXXKlCkLFiyI +iIi4ePEi5TlTFdRsNpeVlYnFYj6fX1NTU1lZaTAYRowYIZFILBZLQkKCVCqtrKxUqVSpqakdHR3d +/60HjZagAPgSj2gw1el0RqNx165d+fn53dffuHEjKCjo0KFDOI47eCiz2WyxWKqrq3sMQ6TRaFgs +llQqdXBgssHBMGzo0KHDhg1jMpnh4eHDhg0TCoV25lW2Q6PRvPDCC8uXL+dyuZMnT16+fPnYsWPV +ajXlOVMVVKVSMZnMgICAxsZG8kNWq9Xk/zufz5fJZCaTCSGk1+tramqGDh3a0NDg/L8FLUEB8CUe +UQPsDBv35Zdfjh079vTp0w4eivybt6mxsVEoFCoUigHn57CQkJD8/PwbN27gOC6VSm/cuCGXywd3 +PyAtLe2pp55atGiRVqv9n//5n0WLFn399dfp6emU50xV0M7OztDQUDtTw1uZTCYGg4Fh2OCqI+1B +AfAlHlED7MwgiGGYQqG4fv26g4cie7TZZLFYDAYD2d3BRZKSksjm5oqKCvLnc3h4uONTpXf3l7/8 +hbyL8P7771+7dg0hNGXKlEmTJlGaL5VBGQwGQRAOdukgPyXnn9uhJSgAvsQj/h6Kiop6rBk5cqR1 ++eeff87IyLA+rGJf70fyu3cWVSqVAoHAycf27RAKheRDUy0tLTqdDiE0fPjwwd2EzMzMXLRoEUIo +Ly9PKpUymcx169a5+n6mM0GFQqFGo3Hk2ZugoCAMwwIDAwdxq9wTggLgS+ivARaL5dq1awcPHhw2 +bBhCKDAwcObMmQEBAdYd1Gp1VFSUI3cmCYJQq9Xjx48npx1nsViRkZHdv8IwDONyuYO7SeugHu0S +zrTgd/9ftlgsly9fHnxarg8aGRnJZrNbWlpGjBgR8R/WR8iEQiG5RiwWJyYmymSyhIQE57OlJSgA +vsR2DbD2dO2N8uZUi8USFhb2+uuvm81mHo+3Z88eBoNRXFzcfZ/Ozk6pVOrI0QICAkpLSwmCYDKZ +5NNNPeYrNpvN5C9096Dwlzsts6Y4HpTJZI4aNaq1tbX7rKLkk7JqtdpoNJJrDAZDeXl5eHg4Jb1M +aAkKgC+x8ReuUCjOnDnT1xs+//zzNWvWUJkBm52bm1teXt7U1MRms7dt2yaRSHrsU1tbGxQU1N7e +bv9BdQaDMX78+M7OToPBwGAwqqqqen/dd3V1sVgsk8nkijYBi8XSo2160FFMJlNQUFD3uxehoaFO +Jef6oCEhIVlZWffu3WMymeSlGPptWxxCSKFQJCcnx8XFUZIwXUEB8Bk2asDhw4dnzJjR18zdBQUF +SqUyKiqKwiQCAgIyMzNjYmJeeOEFmw01zc3Nqamp9+/fHzdunP1DMZlMoVDI4/Fu375t85LFaDQG +Bwd3dXW54itVKpX2GJuouro6NDR0EJXg4MGDDQ0N3dfs3r17woQJg3vKyG1BeTwen88XCoU2r9ui +oqK4XG54eDi1NzZoCQqAb7BRA7Ra7ZIlS/q6at69e7eL2lIOHDjQV0u9RqOJjY2VSqX91gCSRCLp +q80KwzAej6fT6SivAZ2dnTKZrHe46urqgXasq6ioOHLkSI+VKpVq+/btmzdvdipLdwW12Zzo6oGb +aAkKgBcjiJ414LnnnouOjs7NzbXzrgMHDqSlpT3zzDPUJmNnlEq9Xh8eHl5ZWengodrb2/vahON4 +QECAMwPS2WSxWCoqKmz2P1IqlREREY4PVmMymTZt2mSzW9z58+dnzJgxd+5cp3J1fVAmk5mUlNR7 +fXBwMOWfPL1BAfB2PWtAW1tbQkKC/RGB9Hq9KwZd6Kv1CSGkVCpPnTrl+Gg/du5kGo1GuVxO+ajC +dXV1dromkC1CDo6zlJubW1tb29fWnTt3jh07ltq2OMqDVlZW9vUYvuNdvgeKlqAAeDvGzZs3u7+2 +PkphH4vFouUxld7WrVtHdwpOycnJoTsFp8DnTy9v//yPpKTQnYJTltXU0J2CU/Z/tLfn97grxgQF +AADgmejvIwYAAIAuUAMAAMB/QQ0AAAD/BTUAAAD8F9QAAADwX1ADAADAfzFmzZpFdw5Ogee76QWf +P73g+Xp6efv5HxbCh+sAAADwX1ADAADAf0ENAAAA/+URY/4AQNJoNFeuXCkuLq6qqmppadFqtRwO +JzQ0NC0tbdq0abNnz4bZgF2qRa//WSq91dx8T61u1uk6MYzDYkXweGMiI2cnJDyalBTw2ymSgA+A +GgA8wt27d48ePXrt2jWLxdJ9vclk0ul0crn8woUL+/fv37Bhw4QJE+hK0ofdamr6pLj4fH295bfj +n2MWS5fJJOvsPC2R7Lh9e/eMGQ/ExtKVJHAFaAsCHmH58uVXrlzpUQB6aGpqWrVq1aVLl9yWlf94 +/PTpn2Qyi60JMKwatdqnz579sdvUzcAHwHUA8CxisXju3LnTp08fMmQIn89vbm6+evXq4cOHW1tb +EUI4jr/77rsnTpygfAYFQIoLDv7v5OQ5CQnDhMJgDkfe1XVeJtt3547aYEAImQli9aVLeSKRuO/Z +PoB3gRoAPEVYWNiLL764aNGi7o3+8fHxS5Yseeihh5YvX65QKBBCOp3uxIkTq1atoi9T3xTJ4/19 +/PglI0Z0b/RPFAheHD164bBhv/v++watFiHUZTIdKC3dMGkSfZkCKkFbEPAIDz/88PHjx5944gmb +d32joqJWrlxpfXnjxg03puYXFg4bduHJJ/84apTNu75iPv+tbl/6efX1bkwNuBZcBwCP8P7779vf +ITs727rc1NTk4nT8zscPP2x/h+lxcdblBhdMJQvoAtcBwPt4yDymfovTx7zNwBvBvyXwDrdu3bIu +Dxs2jMZM/NO1xkbr8vCwMBozAdSCGgC8AIZhn376qfXl/PnzaUzGDxlxfOft29aXT6Sm0pgMoBbU +AODpcBzfuHGjRCIhXyYnJy9YsIDelPwKbrGszsuramsjX44IC/s91AAfAjUAeDQcx997771ffvmF +fCkQCLZs2QIjRrgNbrH8/dKlH/5TgIVc7v6HH4YRI3wJ1ADgucgC8OOPP5Ivg4KCdu/ePXToUHqz +8h9kAfimtpZ8GczhfP7II8mhofRmBagFz1cAD2U2m99+++3uVwB79uxJT0+nNyv/YbZY/nrxYvcr +gKOPPDIWumf7HKgBwEPt2rXLWgCEQuFHH32U4uVzZnmXDdevWwtAGJf7vwsWpIWH05sScAVoCwKe +KD8//5tvviGXuVzunj17oAC40+XGxqMVFeQyj8U68l//BQXAV0ENAJ7o9OnT1uVnn3121KhRNCbj +h76sqrIu/3Xs2EyRiMZkgEtBDQCeqKSkxLo8d+5cGjPxT7ebm63LjyUn05gJcDWoAcATaTQa67JY +LKYxE//Uotdbl+OCg2nMBLga1ADgiYxGo3UZRgdyPwOOW5dhdCDfBv+6AADgv6AGAACA/4KrbOCJ +bt68SXcKfq3+xRfpTgG4CVwHAACA/4IaAAAA/gvagoAnmjx5snUZ2oXcb0hurnUZ2oV8G1wHAACA +/4IaAAAA/gtqAAAA+C+oAQAA4L/gnjDwRHAfmF5wH9h/wHUAAAD4L6gBAADgv6AGAACA/4IaAFxI +rVYfOXLkypUrPh/UMyl1uo/v3v1ZKvX5oGDQ4J4wcBWz2bx8+fKGhgaE0Ntvvz1//nxfDeqZTBbL +E6dP13V0IIT2zJz5ZGqqrwYFzoDrAOAqarWa/C5GCBUWFvpwUM+k0unI72KE0A2FwoeDAmdADQCu +Eh0dnZWVhRBiMBhu+z1OS1DPFBscPD02FiHEQMhtv8dpCQqcATUAuMr+/fuLiooQQgRBbN++/f79 ++74a1DPtKCi42dSEECIQevPatcrWVl8NCpwBNQC4xKlTpw4dOmQ2m8mXdXV1a9aswTDM94J6pmMV +Ff+4c8dksZAva9ranv/pJ2O3WYJ9JihwEtQA4BJfffVVjzVyufzatWu+F9QzfXbvXo81ss7OX2Qy +3wsKnAQ1ALiEUql0cKW3B/VMiq6u3ivltlZ6e1DgJKgBwCWSk5N7rxw2bJjvBfVMI8LDe69MDQvz +vaDASVADgEs8//zzTOZvzq4xY8ZMnDjR94J6pr+NG8dkMLqvmRgdPS021veCAidBDQDUa25u/uqr +rwiC6L6ysbHx9OnTPhbUM8m12s/u3evxUcg6Or6sqvKxoMB5UAMAxQoLC5ctW3blypUeXwdqtXrL +li1vvfUW7oIHRWgJ6pluKBSPfPPNTzIZ8dv1Sr3+tStXXv7lF/w/z+14e1BACagBgEpKpfLNN9/s ++E9P0d7Onz//6aef+kBQz6To6lpx4UKb0djXDt/dv7/z9m0fCAqoAjUAUOmjjz5qa2uzv8/nn39e +X1/v7UE905b8fI3BYH+fj+7elbS3e3tQQBWoAYAyra2tv/zyS7+7EQRx8uRJrw7qmdR6/RmJpN/d +CISOlJd7dVBAIagBgDLl5eUmk8mRPYuLi706qGcqbmnBHGt2L2hu9uqggEJQAwBlmh3+I3d8T88M +6pkatVoH96Sw3xYtQQGFoAYAygQGBjq4Z1BQkFcH9UxBHI6De/Id3tMzgwIKwRwygDKPPPLI7Nmz +HdmT8dueRF4X1DP9Ljl5UVKSI3tS+EHQEhRQCGoAoAyDwWCz3X1G0RLUMzEYDLbb6xwtQQGFoC0I +AAD8F9QAAADwX1ADAADAf0ENAAAA/wU1AAAA/BfUAAAA8F+Mmzdv0p2DU9atW0d3Ck7JycmhOwWn +wOdPL/j86TVp0iS6U3BKVVkpXAcAAID/ghoAAAD+C2oAAAB4Fp1O57ZY0Mn+NzAMU6vV7e3tWq3W +aDSazWYmk8nhcEJCQiIiIqKionpMWQ6AL/Gl81+r1QYHB7szYszkTyg5DmExjRD8Mv3B/z74vZ6S +A/amuLnCugw14N/a29tlMplare6xHsdxHMcNBoNKpZJIJCNHjgwLC6MlQwBcx8fO/5qamj//+c+r +V69+7LHH6M6lf8Pig2rr1Ez2v0fAzR5JvPnSukOH/uePixZ9/p3LJ7/zmqruakVFRb3/AHowGo3F +xcUtLS3uSQkAt/H2818mk7V3m6uysrJSp9Nt3769pqaGxqwcFGK6uvxR0yMPhJMvfzp9iBM8dPfu +3YrKr90QHWpAT1wuNyEhYdy4cVOnTp0xY8akSZNSUlI4/xn6nCCI8vJyY9/TZwPg1bz0/P/888+f +eOKJq1evki+nTZsWEBCA4/i5c+foTaxfhMUUEsx77521wyPvT58YjRDi8BN+vlLD5/M72jURYTxX +JwA14P9wOJzU1NRJkyYlJSUJhUIOh8NkMgMDA+Pj4ydOnMjj/fsfA8fxhoYGelMFgHJeff7L5XKt +Vrt+/fra2lqEkFAo5PP5CKHOzk66U+sHYcEYTB5C6G9/WxWBrkwbw3hoxthRiRyNRlPf2KRpM7g6 +Abgf8G8ikWj48OGcPqY64nK5ycnJ9+7dI1+q1erk5GQ3ZgeAa3n7+b9w4cLCwkKj0bh58+apU6dK +pdLW1laE0LRp0+hOrR9MNv+778+cv7Zi9tSkj/fttq4nCGL6tGkSecWvkmQG04VTsMF1wL+lp6f3 +9QdA6n4rzAOvhQFwhref/3PmzCGvVMrLy3Nzc3/++Wdy/dtvv/3DDzNvlmsAACAASURBVD/Qmlo/ +sM7asWNGjB4e3WM9g8F4Y/27s6elxKGvxJGOTpg6CFADBsPnZyUEwA4PPP937NhhMNhoNpk8efKO +HTu2bdtGEIT7s3JEorDqzXV/F4v4vTcF8tilTSOe+v0TxroPIkK5LkoAaoCjyEtLEtnUCID/8OTz +/+uvv/7uu+8QQiKRKCAgoPsmqVSam5t7586drVu3emAZMLaVxYrDHps/7dfC+6+9e6KgWNF9qyCY +u3TRyH9dj5z14Gyu5mAw3yUtQlADHGKxWCQSifVldHTPCzcAfJgnn/8tLS379u0TiUSHDh3avHmz +2WzuvvX+/fs7d+7cunVrWVnZjh076EqyL8OjGze9/fevTp57eObEqsp7E8fE9Nhh1qQhh7Y9Uq7O +CgnmZ8WXuCIHqAH9I5+Hs/be5vP5YrGY3pQAcBsPP/8//fRTvV6/dOnStLS0H374wWKx9Njhzp07 +r7/++pYtW/Lz8z3tUVF9h3zs2Mzjx4/putpmTH/A5j6jh0fu3fgQP/GFH0599thMIeU5wHNB/SAI +oqKiQqVSkS/ZbHZ6eroX9ZgHwBmef/5XVFQghM6dO1dSUnLlyhWb+0il0pycnI0bN7766qvp6elD +hgxxb4590uk6EUIf/WP7vg+3xcbG9rXb6OGRK/9fVnTItu+PrY2fuLWhSUthDh70b+mByD+A5uZm +8iWLxRozZkxQUBC9WQHgHl5x/ptMJoRQVVXVxYsXezQEdVdYWHj16tVnn31269atbsyuHwaDHiEU +ExNjpwCQ/mvGMK4gKUQQOWt0P925BwpqQJ8IgigrK7P+AbDZ7MzMTIFAQG9WALiHt5z/Wq2jP4qP +HTs2efJklUpl7U5ML4tZx2axHN//nVUPZM5cfSQ3Z3RqOIVpQA3oU3V1tfUSmMPhjB071gP/AABw +Ea84/zEM02g0Du5sMpkOHDiwYsWKf/7znziOuzQxR+BGVXx8nOP7i0X81NSU8Mi46ent/e/tMKgB +tmk0GrlcTi4zmcyMjAw3j0MLAI285fyXyWQD+ja/fPlyWlqaxWK5du2a67Jy0LTspJEjUvraevXq +VfJWR3cv/D4jbdIfz585GhtF2eO5UANsa2pqsi4nJCR44C8gAFzHW87/4uJiO1tZLFaPzs84jp88 +eXLBggVkfwJ6MQ3VfU1HjGHY5s2b6+WtPdYPGyIMEib++uuvv/8vym5rQw2wraOjw7rsUU9DA+AG +3nL+3759287W6dOn975KuHTp0rx58/Lz860tXXSpKsmbNWtW7/UGg2Hnzp3xCckWVlTvrTMnD5v6 +4JPmtgKqOmtDDbANwzDrMpfrql7aAHgmrzj/MQz79ddf+Xz+iy++OG7cuB5bRSJRQkJC7+4C9fX1 +nZ2dGRkZ58+fd1emNkSF85gMk6yxZcbsxeu2nLKuz83N/fDDD3V6I1+U9fDUYb3fOHfa0BDxA9eu +/JIxQkRJJlADbOt+6njU09AAuIFXnP83btzQarVvvPHG888/z2b/X1en6OjoRx555JVXXvnXv/5l +8423bt2aOHFifn6+uzK1Ydxw5qTsiW+88VZd7b2x6QnkSr1eX1FVX12P1SiC/v6X37HZNj75hFgB +hsKvXbs+a9IA7ifb4aH/ugAAYB9ZnHJzc5cuXXrr1i3yeiU5Ofmrr75avHhxTk5OXzOzV1RUZGVl +FRUVkX0LaFFci2rv172x9q/VlXfmTBu6/8ARo9G44e33Lt8o0Zoi3397eWJ8n12CxVGhIcLwBBE1 +sw1DDQAAeKXp06evXr26qalJrVa//vrrCCGxWLx9+/YzZ86sWLGi+9SSPVRUVKSlpaH+bim7lEKl +Q+IXb9yqXLZs2R//9Nbdu0Wb399+Pq8ga8YfDux+ITkh1M57x4wUjcyY0aGq5HEH0L2gLzBWhG02 +79UA4Ce85fxfunTpokWL9Hp9eXn50qVLn3zyyQMHDnz77bf23yWVShFCQ4cOraiomDBhglsytaG8 +trW8VshhPygKD8xMiP35h/1Pv/jWq3+awWT2c7c3OSE0OGJERUV5auL0kkpnp3eGGgAA8GJ8Pp/P +50+fPn3y5Ml/+9vf7D8pRMJxXK1WJyUl3b9/3w0Z2mcyWxqbO3fnvPqPvf/889O2h43rISaKf+lG +ddewxrRpjzlfA6AtCADgC3JychwpACSlUukhNQAhZNAULl/xVwcLAEIoNjqYHSiurKwakRTW/979 +gesA2/Ly8qzL3nJdDABVvO78LykpGdCckSqVKiYmRiKREARB+7RoY1OYb7+21PH9oyICmRxBk6Jx +eCIFNQCuAwAAXq/fewA96PX68PBwg8HQvTccXcwGZUTYAMZ+4LBZwYJIs9kUHNiz98MgQA0AAHi9 +urq6Ae1vMBjCwsLQb+fIpEuLUjbQt3DYzNCwSIaly/no0BYEAPB6vfsD26fX64VCIUKotbU1MTHR +JTk5LCw86rnnnmN1G0e6+9THNpeThGGNJsygo6CAQQ0AAHi9uLi4srIyx/ffu3fv3r17kWdcB0yd +98bON2cO/H27EUKC4JoOLdbvrnZADbDNK+6DAeAiXnf+b9q0adOmTXRnMUjHvis/9l05XdHhfgAA +APgvqAEAAOC/oAYAAID/8vH7ARiGNTU1BQUFRUZG+nZQAHqzWCxyuZzP55PPQbqIRCIhF4YMGcJm +s63zO4pEouDgYKVS2dXVhRAKDQ3tN41PPvmEXFi2bBmfzz9y5Aj53tmzZ6ekpJw/f76mpgYhNH78 ++OzsbNf973A4nPj4+EG8d1qahhzsp64tQSbXTk03sAgdQogRNPxSQcu4EQEhrCaEUKBw2LW7Oq3O +JaOW4lhb2vAhVXWdDu7vyzXAYrEUFRXp9XqE0MiRI8Visa8GBcDKbDbfv38/MDBwyJAhCoWipqaG +wWBMnz7daDTW1dVFRkaKRNTMPWJFDsGGEIqNjWWz2Q0NDeQUNHw+Pzg4uKWlRalUkjv0WwM+++wz +cuHxxx/n8/nHjx8np4xPTk5OSUnJy8uzTvxCeQ0oKSlJS0tjs9knT568devWsWPHuj+s6aB/fbae +XHhty09HvtVePbOruakBIfRp7rFLBcioKTx7+iOE0Pr160elTMwvbrJ3rAFKS+KX1XYyGMyHJ0dm +JMiZzISK+w498uTLbUEYhpHfxQihtrY2Hw4KgJVKpZLL5bW1tRiGRUZGBgYGxsXFMZnMhoaG5uZm +8nc0tUaMGBESEoIQqqysLC0tNZvN5Pr6+vrS0lJyDOdhw4Y5cln8xhtvjBo1CiG0ZcuWtWvXarVa +cv0XX3yxdu3au3fvIoSWL1/uiseWyIjk/05dXZ1cLh/EQd7a9OnErMlcLrfk+v+MFRca9F1cLpfL +5R4+tH+suLCu4hKLxdr47qb0cXPatUYKkycIYlxS11+fDGSxGDoDeubpPzw0nuj/bQgh374O4PF4 +YWFh5PO/bvs9TktQABBCWq2WyWRGRkZqNBqz2RwQEIAQ4vF4ZGcokUjU1NSUkpJisVja29tDQkK6 +z73ljJiYmNbW1s7OzujoaAaDERX1m1lwGQyGSqUSiURBQUH9Huqxxx67detWWVnZvHnzmEzmnDlz +um/lcDgXLlx48MEHXdGra+bMmZ999tlLL72kUCi4XG5np6NtKd3lnsbHx6W3Xb5YI9Nq2gzozbd7 +7HDu1CdTZ8xfto7qKcwIiwUFPrtkdkBgkUJl1Ou6lM0KhAIdeasv1wCJRGL9JV5VVZWens7nD2BQ +Di8KCkBHR0dhYSGDwZg0aVJCQoK1w5TZbCZb500mU0hIiEgkqq6ubmxsDA8PHzNmDFXRw8LClEpl +Z2dn7/HXMAwLDAzk8XgOHiorK+v8+fMVFRW9D6VSqeLj42NiYijI+LfMZnNpaSlCqLCwECHEYrH2 +7du3cOHC+fPnD/RQQ5Kzf/nll3MFvKu3GntsYjBQmrCRExhOSc7d4cYWlTpiyJAha/86hFwzfVJF +Z1f5j4V9TkZm5bM1QC6XW5spEUI6na6kpCQ7O9ulk6PSEhQAhBCHw2Gz2SwWi8ViEQSh1+uNRiOX +y8VxnKwBbW1tgYGB5J4IIXKZKmKxWKFQNDQ02NyakZHh+J/AokWLTp8+ffz4cZtbd+/e7Yo57g8d +OtTe3v7EE098/fXXCCEcx4VCYVVVFYPBmDdv3oAOdfoGoa4/sWDeQ+MTem4qKSmZPfup7Z8WUpW2 +1aRRxMa1T5Xcq01OSggK5CCEElOzU+q6mKj2bGE/TXA++93U2NizCBsMBrVa7XtBAUAIBQYGPvDA +A0FBQRwOh/yWJ69Hu9cA8se4yWQaPnx4amoqhdEZDAbZ4mSTQCAY0KEyMjL62kreLaCWTqc7evTo +okWL6uvrrSsvXrwolUpLSkqqqqoGdDQcJ5hBw0ba0tXVxQhMLLynpDZ/HGsfmRT2zjsbN3/wHTfg +3/exszPFUx54gM0ifj+7n0sBn70OMBpt3HKxudLbgwJAYjKZHA5Ho9GEh4czGIy2trbo6GiyLQjD +sK6ursDAQKPR2NTUNHnyZLqT9SA6nc5oNO7atavH+hs3bjzzzDOHDh3avHnzgJ4RIghk8xM+f/48 +hlEw2nMPFnPX8GFxM6cuGZacymL938/6hx8Y2qFd/I/ta0YmPW3nGSGfvQ6w2QrvyF0prwsKgFVk +ZKRMJkMIcTgc8sEE8jqAfDiHx+PV19eHhoaSFwqAZGfYuC+//HLs2LGnT592Zz4DZTG1h4UJHp0/ +Y/SInndKfjd3+IRpSycMtTc2tc/WgN5PDggEApf2lKErKABWERERHR0d7e3tbDbbYDCQHaxwHCfb +hVgsllwu7/HcDrAzoySGYQqF4vr16+7MZ6CmTxk9bGhs9zXdn0pf+acFhbd/nTy2zxvpvlkDDAZD +79tTBoOhqYnKThmeEBSA7lgsVmhoqEwmI3/pt7S0oP/UABaLpVAoEEIRERE0Z+lhioqKeqwZOXKk +dfnnn3/OyMggnxfyTBxzQ/fbJARBbN261WD8dy+NoXGC1PQZM8b2+XYfrAFtbW0FBQW978RiGFZZ +WVlWVtZ9TgavDgpAb+Hh4Wq1muypS56Q5AUBeREgFAqp6hbgGywWy7Vr1w4ePDhs2DCEUGBg4MyZ +M8muFSS1Wh0VFXXx4kX6cuwHn2c++c33JWVShFBNTc3nn38ujktVtxmsO0ydOqW48IowJMDm233t +bDAajffu3bP2VOxNqVTyeLykpCRvDwqATUwmk8Vi4TgeEBBgMBis/0UIkQ+Puigu+fSRTQOd5Mtg +MPS1iaxtFLJYLGFhYa+//jrZiWHPnj0nTpwoLi7uvk9nZ2f3Z777ZTIZrINedFdUVDRqwkInE+6B +IAhDR/13PxECUfqoEfhXX59qaMZmzX48LjrYus8js0YdP9o6Izv++ws2Wr18rQbU1taaTP2MxCST +ycRiMYW3amkJCkBfoqOj4+LibG4a6Ly7DrLf5imTyRx/FFWhUJw5c6avrZ9//vmaNWsGnF/f2Gx2 +bm5ueXl5U1MTm83etm2bdQg8q9ra2qCgoPb2djvPv1rhRnXD/aKUlEd7b3rooYfOnDqEUBo1qSOE +EGIwGOlT/hhkunv10g8lBd/XyLkzH1z0+/kju+8THcnncEOnTRD7fg3AMEylUjmyp1wuT0lJ8d6g +ANghl8v7Gu7GRWPZSqXSyMjIvlqZWltbyQ5rjhzq8OHDM2bM6Kt3fUFBgVKppPa2dkBAQGZmZkxM +zAsvvGDzb7m5uTk1NfX+/fvjxo3r92gTUrQv/b/Xp02b1nvTtGnT5s6d+8Tc//r6pwFcVfTr4+M1 +guCw9OTgUJ32L3+an51pY4iasMihQex2m2/3qRrQ2dnpYLN7R0eHVwcFwKPgOB4fH9/XgBDV1dV2 +Wop60Gq1S5Ys6WtAiN27d+t0ukFmadeBAwf6+jGn0WhiY2OlUqkjNYBpbmnT89/9xw2bW/lhqQtm +RFFbAxBCHVoMN6pW/+33mWm2q2N4RJS8QcZkMiyWnl9WPlUDHO+NZafB0SuCAmBHREREX7/3XdFr +/fbt21wu134rk0QiCQkJSUjoNX7Cbz333HPR0dG5ubl29jlw4EBaWtozzzwziFTtIAcltUmv14eH +h1dWVvZ7kJbSHLNoxtdfHsJw262+Br3hwCe7uuQGfuzcwedqS3SYua8CgBASCEKaVbXx4giZvOdY +eD5VAxy/30XhnTFaggJgh16vd+cIJSaTKSgoyP7pbR2ywr62traEhAT7rUZ6vd46pjSF7IztqFQq +T5065cgwwLGRrOAgDgOZ2WzbF/1BIUinw194anRNe3RBSfPg0+1Fq6k9evQouUy2TFjbJwiCUDep +TcaOqIghPl4DoqKi3N//hZagAPQlNjY2Nja2//2o4+B0Lr3HAe3txIkTjhzKFT+nDh065PxBvvr2 +Ur2i//qUnBD6zgc3nQ/X3dTZzz/zTJ+9ACbf1yCEcj6xMWa1T9UAR04y3wgKgOegcFhcV4wJ6k5P +vnyGyXToC8GIOXqDxEGbP7q5+aPB1BWfqgEAAEAjk5n6IeFczQf7CQMAAHAQ1AAAAPBf0BYEAAAU +UNxcQXcKgwHXAQAA4L+gBgAAgP9izJo1i+4cnJKTk0N3Ck5Zt24d3Sk4BT5/eh3x8hGoltXU0J2C +U7z9/A8L4cN1AAAA+C+oAQAA4L+gBgAAgP+CZ0N9CoZharW6vb1dq9UajUaz2cxkMjkcTkhISERE +RFRUFIXd+gHwNHD+DwLUAB/R3t4uk8l6jxZJjtdoMBhUKpVEIhk5cmRYWBgtGQLgOnD+DxpURR9R +VFTU73DBRqOxuLi4paXFPSkB4DZw/g8aXAf4Gi6XGx0dHRERQQ7pbjQa1Wq1VColZzwmCKK8vDw7 +O9vbB2gEwCY4/wcKaoDv4HA4iYmJMTEx3Rs9AwMD4+PjRSJRUVEROZEZjuMNDQ3Jycn0ZQoA9eD8 +HxxoC/IRIpEoOzs7Li7O5l0vLpfb/aR35yRTALgBnP+DBtcBPiI9Pd3+Dt1vhTk+BzIAXgHO/0GD +6wB/BHOfAX8G5393UAP8RWtrq3XZzvTZAPgkOP/7AjXAL1gsFolEYn0ZHR1NYzIAuBmc/3ZADfB9 +5PNwOp2OfMnn88ViMb0pAeA2cP7bBzXAxxEEUVFRoVKpyJdsNjs9PR16zAM/Aed/v+Cz8GXkH0Bz +czP5ksVijRkzJigoiN6sAHAPOP8dAc+G+iyCIMrKyrr/AhozZoxAIKA3KwDcA85/B0EN8FnV1dXW +PwAOh5OZmRkcHExvSgC4DZz/DoK2IN+k0Wjkcjm5zGQyMzIy4A8A+A84/x0HNcA3NTU1WZcTEhLg +Ehj4FTj/HQc1wDd1dHRYl+FpaOBv4Px3HNQA34RhmHUZhskF/gbOf8dBDfBNFovFugxPQwN/A+e/ +4+DTAQAA/wU1AAAA/Bf0D/BNs2bNojsFAGgD57/j4DoAAAD8F9QAAADwX9AW5Jvy8vKsy3BdDPwN +nP+Og+sAAADwX1ADAADAf0ENAAAA/wU1AAAA/BfcE/ZNcB8M+DM4/x0H1wEAAOC/oAYAAID/ghoA +AAD+C+4HeDQMw5qamoKCgiIjI307KAC9dXV1MZnMwMBAl0aRSCTkwpAhQ9hstkwmw3EcISQSiYKD +g5VKZVdXF0IoNDQ0LCzMpZnQAmqA57JYLEVFRXq9HiE0cuRIsVjsq0EB6K69vV0gEDAYDLlc3tra +mpWVxWAwXBdOKpWSC7GxsWw2u6GhgZyChs/nBwcHt7S0KJVKcgefrAHQFuS5MAwjv4sRQm1tbT4c +FIDuKisrS0pKEELBwcE6nc56QrrIiBEjQkJCyLilpaVms5lcX19fX1pa2t7ejhAaNmyYr14Ww3WA +5+LxeGFhYa2trQght/0epyUoAN1FRkbKZLI7d+4YDAYmk2n9UnaRmJiY1tbWzs7O6OhoBoMRFRXV +fSuDwVCpVCKRKCgoyKVp0AWuAzyXRCKx/hKvqqoiGyV9MigAVgRBkDPCt7W1GQwGgiDu37/f1NTk +0qBkI0+nLRiGBQYG8ng8lyZAI7gO8FByudzaTIkQ0ul0JSUl2dnZLp0clZagAHRXV1dnNptjY2Pl +cjlCiCAIDoej1Wqbmppcd2EqFosVCkVDQ4PNrRkZGT78J+Cz/2PerrGxsccag8GgVqt9LygAVjiO +19fXx8TEdL8HoFKpdDpdR0eHVqt1UVwGgyEUCvvaKhAIXBTXE0AN8FBGo9HBld4eFAArs9lssViq +q6vJO1JWGo2GxWJJpVKCIOjKzVdBDfBQfD6/90pX35WiJSgAViaTqa9NjY2NQqFQoVC4Mx9/ADXA +QyUmJvZYIxAIXP14Mi1BAbCy8wyCxWIxGAwajcad+fgDqAGeyGAw9L49ZTAYXPp0BC1BAeiud5cU +8sl9klKpFAgE0G2FWlADPE5bW1tBQUHvO7EYhlVWVpaVlbmiSZSWoAB0RxCEWq0eP3482fzIYrEi +IyO79xDGMIzL5apUKvpy9EFQAzyL0Wi8d++enU4xSqXSOryJVwcFoLeAgIDS0lKCIJhMZkZGBkKI +7CtgZTabdTqdK0KTYwTZZLFYXBHRQ0D/AM9SW1tr57YYSSaTicViCm/V0hIUgB4YDMb48eM7OzsN +BgODwaiqqur9dd/V1cVisUwmE4fDoTC0/TZPmUyWmppKYTiPAjXAg2AY5uB1rlwuT0lJ8d6gANjE +ZDKFQiGPx7t9+zY5cFsPRqMxODi4q6srNDSUwrhSqTQyMpLNtv192NraajQauVwuhRE9B9QAD9LZ +2elgs3uPC2SvCwqAHRKJxGYBQAhhGMbj8XQ6HbU1AMfx+Pj4vgaEqK6uttNS5O2gBngQx3tjGQwG +rw4KgB3kUJ024TgeEBDQ2dlJYbjbt29zudy6ujo7+0gkkpCQkISEBArjegioAR6ExWJRvqdnBgXA +jr7aZBBCRqNRLpdTO4KbyWQKCgqyf3rjOO6rlwJQAzxIVFRUj3FrfTUoAHZMmDDBneGys7Md2c2l +89jQCGqAB6HlJPPVMxsAB/nwmKCO8Ov/eQAA8HNQAwAAwE8RUAMAAMCfQQ0AAAD/BTUAAAD8F9QA +AADwX4ybN2/SnYNT1q1bR3cKTsnJyaE7BafA508vb//8j3j5CFTLamroTsEpn3y0F64DAADAf0EN +AAAA/+XCGlBfX69UKl13fAAAAE5yyVgREolk//79eXl5CKEnn3xy/fr1jg9OCQDwEDqdjsVi+eq4 ++YBEfQ3AcXzXrl0FBQVRUVEGg+Ff//oXQRBvvPGGnakKAQAepaurq6ysrKurCyEUHh6elpZG7bxd +wHNQ3xZUVVVVUFAQGxtbW1tbUlLCYrFOnjwZERFBeSAAgCsQBFFaWkoWAISQRqOprKykNyXgOtTX +APL3AoZhOp2uvb3dYrEwmUz4EQGAt9BqtXq9vvualpYWXx09H1BfA1JSUh588MGWlpa4uLjMzEyC +IBYvXtzjlAIAeKzew4kzGAwYY9xXUV8DcBxfuXIlQshsNhMEwWKxnnnmGbVaTXkgAIArBAcHh4SE +dF8jEon8fJB9H0bxv2tzc/PevXsff/xx6xocx+fPn3/gwAE788MBADwHQRDR0dHWlwwGA2aa82FU +fi/rdLpt27Zdv349Li7u1VdfnTZtGoZh58+f37Nnz8GDB1Uq1YYNG6BVEQBPZjAYysvLu8/qTt4i +FolEI0aMgF9yvofKf9Hy8vLr16+np6dfunTJ+iDQ1KlTly1bNmXKlO+++27x4sXJyckURgQAUAjH +8eLiYp1O13uTSqXCcTwjIwNuDPgYytqCMAx79913EUKHDh3q8SRoUlLSjh07EEK7du0KCgqiKiIA +gFoSicRmASBpNJqmpiZ35gPcgLIaIJPJlEplampqVlZW761PPfUUk8m8c+dOaGgoVREBABSyWCz9 +fsU3Nja6JxngNpTVgM7OToSQSCSyuZXH4wUHB1ssFugtDIBn0ul0/f55arVai8XinnyAe1BWA8Ri +MUKoqqrK5mnU2NjY0dFBVgKqIgIAKOTg7zP4GedjKKsBMTExEydObGlpOXToUO+tu3fvRgjNmzfP +YDBQFREAQCEej9fvPtDn3/dQ2T9g48aNCKHVq1d/+eWX1pUEQezZs2fPnj0sFmvNmjUtLS0URgQA +UIXH4/H5fPv7hIeHw3NBPobKGhAZGTlv3jy9Xr948eKxY8e+/PLLf/rTn1JSUl555RWCIF588cXE +xEQMwyiMCACg0NChQ+1sZTAYQ4YMcVsywD0o6x+Qn59/9uzZs2fPki/Lysqam5txHLeOErF///6u +rq6VK1cSBEFVUAAAhaKiouRyeVtbm82tMTExQqHQzSkBV6PgOsBsNl+7dm3VqlVnz56NiYl55513 +CgsLdTqdQqFQKpWdnZ15eXkvv/xycHDw0aNHp02bVltb63xQAAC1NBpNQUFBXwUAISSXy+/evWsd +Uxr4BmdrgNlsPn78+Kuvvsrj8bZu3VpXV7dx48Zx48ZZ+5QHBQXNnDlz3759Uql0xYoVZrP56aef +JqcYAwB4AovFUllZWVxcrNVq7e/Z2tpaUFBQX1/vnsSAGzhbA/Lz8z/66CORSHT58uV169YFBAT0 +tWd4ePjHH3984sQJDoezbt06iUTiZGgAgPMsFktpaalCoXBwf4Igamtr4WreZzhVA/Lz81955RUu +l/v999/b7B7c21NPPXXw4EGE0AsvvADzlAJAu5qaGo1GM9B31dfXy+VyV+QD3MypGnDu3DmE0Pr1 +6ydNmuT4u5YtW7ZkyRKdTrd37154zgwAGrW2tg76q7ympgbmhvIBg68BhYWFZ86ciY6OXrNmzUDf +u3XrVjabffjwYYFAMOgEAABOcqZJx2Kx1NXVUZcLoMfgawA5vuBzzz0XGBg40PcmJibOmzcPx/FL +ly4NOgEAgDPa2tr6vQlsn1KphB4/3m7wNeDUqVMIoUcffXRwO9j10gAAIABJREFUb1+4cCFC6OLF +izCaNAC0UCqVTh6BIAiVSkVJMoAug68B+fn5CKHMzMzBvZ18Y3l5OYwiBwAtuk8WRu9BAI0G2U/Y +YDBgGMZisdavXz+4I5BdUVpbWwfRlAQAcJ6d6WIcB13GvN0ga0BAQACLxcJx/MMPP3QmvEAgYLFY +zhwBADAIOI5TMmoLDCXt7QbZFsRkMmNjY50PHxcXB8MHAeB+TCaTkiezYZZ5bzf4f7/i4mKhUIjj ++KCPwGQyEcxOBwAdGAwGh8Nx/qkeO0MDAK8w+BpQWVlJYR4AADebMmUK3SkA+sF1HAB+CnrpA0Tt +HDIAAAC8C9QAAADwX/3UALVaLZFIKHmO2HEtLS3V1dUdHR3uDAqA78EwTCaTuXkSb1qCgkGzVwPM +ZvM777yzc+fOxx57TCqVuichDMPy8vIefvjh1tbWiooK9wQFwPdYLJaioqL79++XlpY2NTX5cFDg +DHs1QK1WFxQUfP755wsXLjx37px7hvtvaWkJDg6OiYn53e9+l5eXx+Px3BAUAN+DYZh1bGc7M0T6 +QFDgDHvPBUVHRz/++OMZGRkEQfzyyy8mk8kNCcXGxp46dWrjxo3kBDUwKiEAg8Pj8cLCwlpbWxFC +YrHYh4MCZ9i7DqioqPjhhx+EQiGbzV61apUbniTDcfz69etHjhx59913z549++c//9mZPmgA+DOJ +RGL9JV5VVeWegX1oCQqc0WcNaGlpefXVV7///nuNRqNSqTIzMxcvXuzqcZ6lUukrr7xCEMT169dX +rlwpkUgeffRRPp/v0qAA+B65XC6VSq0Dseh0upKSEovF4ntBgZP6bAvasWPHW2+9NWfOHISQUCg8 +ePBgXFycRCKJjo52XTZbtmx5+umn2Wy2WCxetGhRR0fH7du38/Pz09PTXRcUAN/TewgWg8GgVqtF +IpGPBQVOIfquAWVlZW+99Zb1ZUBAQHJyskQiEYvFrhvlTSKRHDt2DMdxjUYjFApXr1594cKFxsbG +zMxMGJ4QAMcZjUYHV3p7UOCkPtuCsrKyfvrpJ+tLlUpVUlIyatQol2YzY8aMzMzM8ePHc7nc27dv +jx8//rXXXsvOzobLSQAGxGYLqqvbcmkJCpzUZw14/fXXP/jgg40bN5aVlf3888+PPPLI2LFjJ06c +6NKhnt966y1yGMJ//OMfX3zxBUJo3rx5WVlZUAMAGJDExMQeawQCQVhYmO8FBU6y3RbU3Nx8+fJl +s9l88ODBX375pb29vaqqKjQ09PLlyy593ovFYr322msmk2njxo0VFRX//d///dFHH9XV1bkuIgC+ +x2AwNDQ09F7Z1NQUExPjS0GB82zUgMLCwi1btmRlZf36669ZWVnkyo6OjsOHD//xj3989tlnlyxZ +4rrJv/Lz83/88UcGgzFu3Lg333zzwoULGRkZLooFgO9pa2srLS3tff8Mw7DKysrW1ta0tDTKn/Om +JSigRM+2IKVSmZOT8/zzz588edJaABBCAoFg5cqVRUVF33zzzZUrV1yXUHNzM9nBBCHU2NiI4zic +OgA4yGg03rt3z84DFEqlUiKR+EBQQJWeNeDWrVujR4/euHGjzb2TkpJOnDjx5ptvumgIB5PJxOFw +zp49S748derUyJEjYbJJABxUW1vbb39+mUxG7SiQtAQFVPlNDWhtbd2xY8eGDRvs/PSeOnXqQw89 +dPToUXImSGodPHhQLBb/4Q9/IF9+/PHHK1euhAlLAXAEhmEqlcqRPeVyuVcHBRT6zfc4n88XCAST +J0+2/55HH330ypUrlN/ur6io+P777z/44ANrBZo6deqsWbNOnDhBbSAAfFJnZ6eDF80UDsxOS1BA +od/8xNZoNAwGY926dfbfU1VVJZPJQkJC1Go1VXmYTKajR4+uX78+JSWl+/qtW7dmZGTMnTtXIBBQ +FQsAn+R4byyDweDVQQGFflMD2Gx2c3Pztm3b+n3b8OHDqW2iyc3NPX/+vMFg+Pbbb3tsslgsf/jD +H3788Uf3DFwKgJdy/Gk9Cp/royUooNBvvseHDh2K47gjHbJYLJZSqaQwjzfffPOzzz6zs0NbW1tl +ZSWFEQHwMVFRUVFRUf4QFFDoNzWgq6vr1q1btOTR2tr666+/0hIaAN9Ay1PU8Oi2t4M55QEAwH9B +DQAAAP8FNQAAAPwX1AAAAPBfUAMAAMB/QQ0AAAD/xZg1axbdOTglJyeH7hSc0m+vbA8Hnz+94POn +l7d//qHBfLgOAAAA/wU1AAAA/BfUAOCh6uvrqR2PBDgOxnfzH1ADgMeRSCTr1q37/e9/v2jRop07 +d3K5XLoz8i9Go/Hjjz9WKBR0JwLcAWoA8Cw4ju/atSsvLy8qKkogEPzrX//avHkzzCPkUgwGo6ur +y/pSpVL9/e9/P3v2LHzs/gBqAPAsVVVVBQUFsbGxtbW1JSUlLBbr5MmTERERdOfly06cOHHnzh3r +gMH79u1LS0vbvXv3xx9/TG9iwA2gBgDPwuFwEEIYhul0uvb2dovFwmQyyZXAFYxGY0BAwCuvvHL5 +8uXAwECE0IgRI5qbm/l8fktLC1wK+DyoAcCzpKSkPPjggy0tLXFxcZmZmQRBLF68WK/X052XzzIY +DHw+HyG0cuXKo0ePqlSqUaNG6fV6jUajVCrNZjPdCQLXgiIPPAuO4ytXrrx48SL57cNisZ555hkK +Zy0FPQiFwrNnz65YsUIkEm3fvt26niCIqVOnnj59es6cOXBb3ofBdQDwIM3NzXv37n388ceta3Ac +nz9//oEDB6BRwkWKi4uTk5OFQmGP9QwG4/3334+Ojn733XdhGkgfBjUAeAqdTrdt27YTJ07ExcXt +3r07Pz//6tWrGzduFAqFBw8efO+99+CbyBUuXry4atWqgICA3puYTOYjjzzyu9/9bsWKFfDh+yqo +AcBTlJeXX79+PT09/e7du3//+9+zsrKmTp36zjvvFBYWRkVFfffdd1VVVXTn6Gtu3rwZEhKSlZUl +l8vPnz/f3t7efSubzRaJRFOnTp0zZ86aNWuYTPi68EHwjwo8AoZh7777LkLo0KFDPZ4ETUpK2rFj +B0Jo165dQUFB9OTnowoKCl566aULFy6MGTOmpKSkd4tQaGjo8OHDly5dGhgYePLkSVqSBC4FNQB4 +BJlMplQqU1NTs7Kyem996qmnmEzmnTt3QkND3Z+bD6urq8vMzDx27JhGo5k8ebLNffh8fnJy8pYt +Wz7++GOdTufmDIGrQQ0AHqGzsxMhJBKJbG7l8XjBwcEWiwUeVaRWR0cHQuj9999vaGiYMmVKX7vx ++fzExMRDhw49++yzNu8cAO8FNQB4BLFYjBCqqqqy+S3f2NjY0dFBVgK3p+bLyN/1MTExsbGx9vcM +CwsbPXp0VFTU7du33ZIacBOoAcAjxMTETJw4saWl5dChQ7237t69GyE0b948GM+SQp2dnQO6zTt0 +6NCcnJwNGzbweDzXZQXcDGoA8BQbN25ECK1evfrLL7+0riQIYs+ePXv27GGxWGvWrGlpaaEtP5/T +2NgYHx/v+P4BAQEpKSlDhw4tKSlxXVbAzaAGAE8RGRk5b948vV6/ePHisWPHvvzyy3/6059SUlJe +eeUVgiBefPHFxMREDMPoTtN3REdHJyUl9bX16tWrFRUVPVaKxeJXX301NzcX7gr4DOh7CTxCfn7+ +2bNnz549S74sKytrbm7Gcdw6SsT+/fu7urpWrlxJEAR9afqUgoKCSZMm2dyEYdjmzZs3bdrUYz2P +xxs5cuTNmzcDAgKgHvsGuA4ANDObzdeuXVu1atXZs2djYmLITmE6nU6hUCiVys7Ozry8vJdffjk4 +OPjo0aPTpk2rra2lO2UfcebMmVmzZvVebzAYdu7cmZqampiY2HurSCT64x//+PPPP7s6PeAeUAMA +ncxm8/Hjx1999VUej7d169a6urqNGzeOGzfOOjpQUFDQzJkz9+3bJ5VKV6xYYTabn3766by8PFqz +9gUsFstgMLS1tT333HPXr1+3rs/Nzf3www8xDJszZ05kZGTvN4aGhi5YsOCnn34iRxsF3g5qAKBT +fn7+Rx99JBKJLl++vG7dOjutzOHh4R9//PGJEyc4HM66deskEok78/Q9ra2tEyZM2LBhw927d6Oi +osiVer1eoVAEBATEx8fPnTuXwWD0fiOPx4uLi7t+/XpISIh7UwYuATUA0CY/P/+VV17hcrnff/+9 +ze7BvT311FMHDx5ECL3wwgswoLEzIiIiZDLZ8uXL8/Pzw8LCvvjiC6PRuGnTpqqqqsTExKefftrO +A6ACgUAkEjU1NbkzYeAiUAMAbc6dO4cQWr9+fV93Jm1atmzZkiVLdDrd3r17bf5QBY4wmUzbt2+v +q6t79tlnc3JyioqKdu7cWVJSsmLFikcffZScUKwvfD5/7ty5d+7cgc/fB0ANAPQoLCw8c+ZMdHT0 +mjVrBvrerVu3stnsw4cPCwQCV+TmJwwGw5QpU1avXv2HP/zh+eefr62t3bZt2wMPPNDvVA08Hm/c +uHFlZWX2SwXwClADAD3IUQqee+65QXyPJCYmzps3D8fxS5cuuSA1/0IQhMFgWLFixWuvvTZq1ChH +ftoHBAQ0NjZWVVXBMK4+AGoAoMepU6cQQo8++ujg3r5w4UKE0MWLF+FryHkXLlxYtWpVWlqag/sH +BAQkJiZWVVXBdYAPgBoA6JGfn48QyszMHNzbyTeWl5fDKHLOa2trW7BggeP7czic8PDw+vp6GDjI +B0A/YUADg8GAYRiLxVq/fv3gjtDW1oYQam1thZ+izmtoaBjQtzmTyRSLxSaTyWg0ui4r4B5QAwAN +AgICWCwWjuMffvihM8cR/P/27jyuiTNvAPiEkISEI0AK4Q6X4AEVEZCWGjxAXa3rUbWt5y7btdrW +7q52q6VuwT0qFf/Qdiv99CNSKSKu1IuKFgtSaJVbLIIUIhGQIxwJR0Ig17x/zL558yIilklmJvl9 +/5rJjPk9Pkye3xzPPI+DA8xzO30tLS3P+k9oNJqrqyuWiQGlQQ4ABLCysvLw8Ghvb5/m93h6esLw +QdPn5ub2+9//3jCbGtbqhMt+fn4qlaqvrw8Gj6M6yAGAGD///DOXy9Vqtb/6G7Cx7zs6OvArlIU6 +duzYJAOIPslHH32EIEhVVdV0/oiAaCjkAECMX375hegigP/q7e3t7e0luhSAGNAvCAAALBfkAAAA +sFyQAwAAwHJBDgBG1N/fLxaLsWEhzDsoOanVatPP9tXT0/PUEYcAeUAOAMai0WiSkpKOHj26Zs2a +1tZWMw5KKnK5vLe3VyaTIQgyPDwcHh5+584dHo/36NEjqVSqVCqNFBdFUZ1OhyAInU4vLi6GV4ip +AnIAMJb+/v6qqqrMzMyXX375+vXrphnun5CgpDI2NiYUCnt7e+3t7V1cXDIyMjQaDYqiNjY2cXFx +tbW1Hh4euAdFUfTBgwfYCx90Ov3111+HPrtUAZdswFj4fP769etDQ0NRFC0qKlKr1eYalCSam5sZ +DEZgYGBWVpZMJgsPD9dqtdbW1h4eHlKp1NnZ+Z///OfChQuVSmVNTU1wcDCOk0FqtVoOhxMRESEW +i21sbORyeWdnJ4/Hw+v7gfHAdQAwlsbGxqtXr3K5XGtr63fffdc0840QEpQkXF1dx8bGaDSara2t +UqnEbvtwOBy5XI4gyA8//ODq6hoQEJCWlvbyyy+LRCIcJ4Ps7OxUqVTe3t5CoTA2NjYoKMjd3f3O +nTt4fT8wHsgBwCj6+vr27duXl5cnlUp7e3vnzp376quvGnucZ0KCkgSDwWhra1MoFBwOx8HBgclk +YvfB2Gw2lgOGhobs7e1HR0djY2OrqqrUarWzszNe0bu7u5cvX97a2qp/Z1goFHp7e5eVleEVAhgJ +5ABgFKmpqQcPHoyPj0cQhMvlpqenDw0NGXsieEKCkoRarV60aFFra+vQ0BCXyw0ODhaJRAiC0Gg0 +rF2OiYnR6XRjY2O3b9+2trbesGEDXrfs+/v7ORxOUlJSaWkpNoAHgiD29vYxMTEajQbLQIC0IAcA +o2hoaAgJCdGvMpnMgIAAsVhs1JszhAQlj/7+/pCQEK1Wa2trS6fTsTnfdTqdlZWVTCZjMBgcDkej +0Xh5eS1YsKC1tVWj0eASd2BggMPhbN68edWqVYZV7ejouG3bts8//xz6CJEZ5ABgFJGRkQUFBfrV +3t7eurq62bNnm19QUnF3d3/w4AGCICiKjo2NYf30raysOByOo6Ojr69vbm6un58fiqI4jrfa39/v +4OCwYMECJyencZtcXV137979448/4hUL4A5yADCK/fv3Hzt2LDk5uaGh4caNG8uXLw8LC4uIiDDq +UM+EBCUVR0fHtrY2BEFQFPXy8qLRaCwWi06nazSanp4eFou1ZMkSOp3e39+PY9CgoCA+n2/4ieG8 +AgsXLrx16xaOz58BvqBvKMCfRCIpKSnRaDTp6elFRUWDg4NNTU2Ojo4lJSVubm7mFJRsNBqNp6fn +L7/8YmNjIxQKc3NzAwMDraysUBTt6+vr6+sTCAQRERHNzc04Br1///7OnTv1qyiKHj58+PDhw9iz +ARsbm+XLl3d3d+PYFRXgCK4DAM5qamrefvvtlpaW8vLy9vb2kpKSu3fvSiSSxMTE7du3nzlzxhjD +zRMSlJxsbGwqKiqqq6t5PF5dXR2Hw5FKpRwO58GDBw8fPiwtLcV9AkiFQnH16tXOzk4EQUQiUWZm +5uzZsw3fzHjhhRcKCwthxjdygusAgKeenp6UlJSEhIRDhw4Zfu7g4LBnz55Vq1bFxsZ6enouWrSI +6kFJi8lkNjc329jY8Pl8nU5XWVnZ0dExPDzs5+f31ltvFRYW4ttRB0VRkUhEp9OjoqK0Wu2VK1fo +dPprr71m+Ia2QCAYHBzkcrlSqRTH0AAXkAMAniorK0NCQpKTkyfc6u/vn5OTIxQKa2pqRkdHKR2U +zGJiYvbs2aNSqfSn/DQazc7ODkEQ3B/P0mi0vXv3FhQUXLx4UalUuru7r1ixYtzjASaT6erqymaz +8Q0NcAE5AOBGJpOlpqbevHlzkr6YMTExS5YsycrK2rRpEzbEGBWDkpxUKn38rQhspjBjdNMcGRmJ +jY2l0WgSiWThwoUTPv4NDAzE+qoCsoEcAHBja2vr4OAQHR09+W6rV6/Ozs5+8803cemdQkhQkmtp +aampqZlwk2GPHRxptdqurq64uDjsauNxfD6/tbXVy8vLGNHBdEAOALiRSqU0Gu3AgQOT79bU1NTW +1mZvb49Lc0xIUJILDw9/4403Jtx0/fp1IwUdHBx8UgJAEMTe3r6lpSUgIAD3J9JgmiAHANxYW1tL +JJJPPvnkqXsGBQXhNc0IIUFJLjc3VyKRTLjJwcHBSEHr6+uzsrKwZeyFDP1rGSiKqlQq7F1lyAFk +YxE/CWAaAoFAq9VO5YY7nU7v6emhblAyCwgIqK2tHR4ennArk8nEOnHi7s9//vMkMxNg07o9evTI +GKHBdEAOALhRKBSVlZWWEJTMlEpldXW16eO2t7djc8gAaoF3xAAAwHJBDgAAAMsFOQAAACwX5AAA +ALBckAMAAMByQQ4AAADLRaP6pM9PfUGU5FJSUoguwrRA/RML6p9YVK//L/79KVwHAACA5YIcAAAA +lgveE/5/vLy8vL29W1tb6+vrOzs7BwcHmUymi4vLvHnzgoODa2pqYLQTMAkGg9HS0lJSUnL79u22 +traBgQEmk+nm5iYUCtesWePm5mY5MxsTAur/V4Ac8F9RUVENDQ3Z2dnjRp5RqVRyuRwbjd3Hx2fT +pk1VVVUElRGQl06n++yzz86dO/f48SMSiUQi0alTp/z8/DIzMxkMBlGFNGNQ/78a3Av6ryNHjnz7 +7beTDz3W1tZ27NixqKgok5UKUMWLL7549uzZyY8fsVi8ePHiJ43mBqYD6v9Xg+uA8Xx8fFavXs3h +cDo6OmQymUAgcHBwyMjIwKZh0mg0J06cWLNmTUdHB9ElBWTk6+u7e/duoVDIZDK1Wi2dTq+rq3v/ +/fexIUs1Gs26deuKi4s1Gg3RJTVPUP/PCq4D/o+rq+uBAwdmz55dX19fWVnZ2dmpVCobGxsrKirW +rl0rEAiw3eRy+SRzZQCLxefzz549+5///AebWFGtVut0OrVaPXPmzOvXr/v6+mK7yeXygoICQktq +nqD+fx3IAf+1fv36uLi4srKyCacdb25u3r59u361tLTUhEUDFJCQkJCfn+/n5zfh7QiVSpWWlqZf +PXfunAmLZhGg/n81uBf0X1KpdPIdmpqa9Mutra2urq5GLhGgkp07d6rV6kl2MDxgRCKR8UtkWaD+ +fzW4DpgqlUqlX4auBeBZGZ6fwvFjelD/TwI5YKoCAwP1y3PmzCGwJICKZDKZfjksLIzAklgmqP8n +gRwwJTqdLj8/X78aFxdHYGEA5ahUKsOBZXbu3ElgYSwQ1P8kIAc8HYqizs7O9fX12GpISIjhswEA +JqfVanNycmpqarDV0NDQmTNnElskiwL1PznIAU+BoqiLi8ulS5ewVScnp+3bt3d1dRFbKkAVWq32 +4sWLJ06cwFadnJwyMzMNny0Bo4L6fyrIAZPBEkBubi62am9vn5KSYnhTCIBJaLXay5cvHz16FFu1 +t7fPz8+HIadMBup/KiAHPBGKoo6OjvoE4OTkdPz48bNnzxJbKkAVGo3m/PnzR44cwVadnJxu3LhB +o9GILZXlgPqfIsgBT+Tp6Xn58mVsmcfjHT9+PDMzk9giAQopKSk5duwYtszj8QoLC4ktj6WB+p8i +yAETmzFjRnZ2NrbMZrOPHz+ekZFBbJEAhajV6sTERGyZzWbn5+fDPWhTgvqfOsgBEzPsTbxv377T +p0/DyONg6s6fP69fTkpKIrAklgnqf+ogB0zMcJIAFos1+WvoAIxj2HFgxYoVcAJhYlD/Uwc5YGIS +iUS/fPfuXQJLAqjIcGhxrVZLYEksE9T/1EEOmJhSqdQvP3U4OQDGMTx+4CLS9KD+pw5ywNNNPjkR +AABQF+QAAACwXJADJoYaILosgHrg+CEW1P/UwRwyE1u8eDHRRQAUVl5eTnQRLBrU/9TBdQAAAFgu +uA6YWHFxsX550aJFhJUDUFN0dLR+uaysjMCSWCao/6mD6wAAALBckAMAAMByQQ4AAADLBTkAAAAs +F+SAiUH/YjAdcPwQC+p/6qBf0MTg/QAwHdA/nVhQ/1MH1wEAAGC5IAcAAIDlghwAAACWy8xzwNjY +2IwZM8LDw80+KDCG/v5+sVg8MjJi9kHJCerfBMz5mbBOp/Pw8Pjoo49u3brF4/Fu3LhhrkGBMWg0 +mqSkJBRFm5qaTp48KRAIzDUoOUH9m4Y5XweMjY35+Ph4eXmtW7euubnZ29vbXIMCY+jv76+qqsrM +zHz55ZevX7/OYrHMNSg5Qf2bhjlfB7DZbJFIlJycXFFR8c477/z73/8216DAGPh8/vr160NDQ1EU +LSoqMs2UhIQEJSeof9Mw2+sAFEXd3Ny+/fbbQ4cOXbt2bdeuXXPmzDHLoMBIGhsbr169yuVyra2t +3333XRqNZq5ByQnq3zTMNgcEBATk5OSgKHrlypWtW7e2t7d/9dVXUVFR5hcUGENfX9++ffvy8vKk +Umlvb+/cuXNfffVVDodjfkHJCerfZMw2BxQXF2/ZsmXHjh0SicTe3n7Hjh1ubm7u7u7mFxQYQ2pq +6sGDB+Pj4xEE4XK56enpQ0NDYrHY/IKSE9S/yZhtDujq6oqJiWGz2Xl5ee3t7Ww2e+7cuQMDA46O +jmYWFBhDQ0NDSEiIfpXJZAYEBIjFYqPeHCAkKDlB/ZuM2eaAiIiIt95664svvpgxY4ajo+MXX3xx +5syZBQsWjI6OmllQYAyRkZEFBQX61d7e3rq6utmzZ5tfUHKC+jcZs+0XJBQKy8vLVSqVSCRqbm5G +EOQ3v/mNg4ODUZtjQoICY9i/f39cXByHw9m0aVNHR8f+/fvDwsIiIiKamprMLCg5Qf2bjNnmgJ9+ ++mnr1q3PPfdccnJyY2Pj2rVr//GPf6SkpJhfUIA7iURSUlKi0WjS09OLiooGBwebmpocHR1LSkrc +3NzMKSg5Qf2bktnmAARBamtrq6qqaDTavHnzEhMTCwoK+vr6zDIowFFNTc3HH38cGRlZXl4eGRmJ +fTg0NHT69Ont27fv2LHjtddeo9PpZhCUnKD+TcxsnwcgCCKXy2UyGbbc0dHh7OxsbW30nEdIUICX +np6elJSUhISECxcu6NsCBEEcHBz27Nlz586dixcvlpaWmkFQcoL6Nz2zzQE6nY5Op1+7dg1bvXTp +ko+Pj0ajMb+gAEeVlZUhISHJyckTbvX398/JyUlMTLSxsaF6UHKC+jc9s80Bra2trq6umzdvxlbT +0tLee+89Z2dn8wsK8CKTyVJTU//2t79N0hcwJiZmyZIlWVlZVlb4/HYICUpOUP+EMMP/EoIgw8PD +Mpns5MmT+r9rTEzM0qVLHRwczCwowJGtra2Dg0N0dPTku61evbq0tNTJyYm6QckJ6p8QZnirWqfT +aTSaAwcOBAYGGn5++PDh0NDQLVu2/Pjjj+YRFOBLKpXSaLQDBw5MvltTU1NbW5u9vX1/fz9Fg5IT +1D8hzDAHPHz4EPtrfffdd+M26XS6tLS01atXP3z40AyCAnxZW1tLJJJPPvnkqXsGBQXh9aifkKDk +BPVPCDP8L+3YsePvf//7JDuUlZV98MEHZhAU4EsgEGi1Wp1O99Q96XR6T08PdYOSE9Q/IcwwB5SW +li5evNgSggJ8KRSKyspKSwhKTlD/hDDPZ8IAAACmAnIAAABYLsgBAABguSAHAACA5YIcAAAAlgty +AAAAWC7aokWLiC7DtFB9dP6nvqBIclD/xIL6JxbV69/RjgPXAQAAYLkgBwAAgOXC+T1hLy8vb2/v +1tbW+vr6zs7OwcFBJpPp4uIyb9684ODgmpqasbExfCN4IorFAAAZdElEQVTii8FgtLS0lJSU3L59 +u62tbWBggMlkurm5CYXCNWvWuLm5oShKdBktgkajaWxspNFoc+bMIbosFoTqxz/V2x9C4JYDoqKi +GhoasrOzx428oVKp5HK5WCxGEMTHx2fTpk1VVVV4BcWRTqf77LPPzp0793j5RSKRSCQ6deqUn59f +ZmYmg8EgqpCWQy6Xv/HGGywW6/79+2Y5SAvZUP34p3r7QyDc7gUdOXLk22+/nXzopba2tmPHjkVF +ReEVFEcvvvji2bNnJy+/WCxevHjx8PCwyUplgfr7++/evdvY2IggiEaj+eabb2pra5lMJtHlMnNU +P/6p3v4QCP8x43x8fFavXs3hcDo6OmQymUAgcHBwyMjI6O3tRRBEo9GcOHFizZo1HR0duIfGha+v +7+7du4VCIZPJ1Gq1dDq9rq7u/fffx85GNRrNunXriouLYYZIIxkeHv7000+xa3adTnfy5EkEQb77 +7rvu7m6ii2YRqH78U739MT08c4Crq2tCQkJtbW19fb3+Q+yEbu3atQUFBa2trQiCyOVyOzs7HOPi +hc/nHzt2LCAgADubUKvVCILodLqZM2dev359/fr12AQAcrm8oKBgyZIlxJbWXPn6+p45cwZBkLCw +MCaTWVhYKJVKpVIp0eUyf1Q//qne/hAFt3tB69evj4uLKysrGx0dfXxrc3Pz9u3b9aulpaV4xcVL +QkJCfn6+n5/fhJeTKpUqLS1Nv3ru3DkTFs3i1NbWbty40d/f39nZecWKFRqNRqVSEV0oM0f145/q +7Q+BcMsBUqm0s7Nzkh2ampr0y1hCJpWdO3diJz5P4urqql8WiUTGL5EFGRsbe/ToUXd3N3aH4fz5 +87/97W9v3rx548aNqKio1NRUOp0+NjbW2dnZ2dlJ2rsQlEb145/q7Q+BTDeHjOGpHDm7FkzO8PyI +iuUnIZ1OV1BQcP/+/QsXLmANEJvN3rhxY0FBwSeffNLV1YUgSFBQ0JEjR5RK5bfffou1/hwOZ8OG +DX/4wx9YLBbB/wFLQvXjn+rtj/GY7h0xw8nWqdjpWyaT6ZfDwsIILIl56O3t/fzzz5OTk7H+iAKB +wNvbe2xsLDMzU61W66/Wy8vLpVLppUuXEATx8/Pz8PBQKpWZmZmxsbGnTp2CH7PJUP34p3r7Yzwm +ug7Q6XT5+fn61bi4uB9++ME0oXGhUqkMBzbZuXMngYUxA1Kp9C9/+YtIJHJ3d//oo49Wrlyp0Wg0 +Gg2NRisqKvroo4/eeuutDRs2aLXaixcvBgQEJCYmrly5UqlUqtVqFEVv3LiRmJj45ZdfVldXf/XV +VwqFguj/kJmj+vFP9fbHqExxHYCiqLOzs/5hfUhIiOG9OfLTarU5OTk1NTXYamho6MyZM4ktEqWh +KJqdnS0SiaKjo3/66afIyEiVSmVra+vk5MRkMiMiIr7//vuIiIjz589fuHBh2bJlt2/fnjdvnkQi +YTKZzs7ODg4OS5cuvXnz5qxZs6qrq0+cOGFtbYbTYpMH1Y9/qrc/xmb0HICiqIuLC3YtjyCIk5PT +9u3bsVu9lICdip44cQJbdXJyyszMhG4q06FUKrOyslxcXL7++uuhoaFZs2YFBgby+XwXFxeBQBAW +FsZms99//31s53feeUcsFvP5/Oeff97T0/O5555zd3efNWuWv7//6dOnbWxs0tLSrKxg2Ctjofrx +T/X2xwSM++PB/gC5ubnYqr29fUpKiuFFGclptdrLly8fPXoUW7W3t8/Pz4chR6YpMzMTQZD33ntv +cHAwODiYw+EYbqXT6f7+/nQ6Xf8Jm8328PCg0WiGuzk5Ofn7+3/wwQcIgmDJwCRltyxUP/6p3v6Y +hhFzAIqijo6O+j+Ak5PT8ePHz549a7yI+NJoNOfPnz9y5Ai26uTkdOPGjXEtEfgVLl++jCDIypUr +n3vuuam03U+qcx6Ph81+UVpa6uTkhGsZAeWPf6q3PyZjxBzg6emJ/doRBOHxeMePH8dOAKmipKTk +2LFj2DKPxyssLCS2POZBoVDIZDIGg0Gj0abfcPN4PARBHj16ZGtri0fpwP+h+vFP9fbHZIyVA2bM +mJGdnY0ts9ns48ePZ2RkGCmWMajV6sTERGyZzWbn5+dT6B4omWE3ebRarVqtnn7PTq1WiyAIg8Ew +vHcEpo/qxz/V2x9TMlYOMOxNvG/fvtOnT5N85PFxzp8/r19OSkoisCRmxsbGxsvLS6fTqVSq6Tcr +2AufgYGB8PIwvqh+/FO9/TElY+UAw0G6WSzW5K+hk5Dhg6MVK1bAAYSjNWvWIAhy9erV6Y8Eh/X3 +WL58+cjICA4lA/+L6sc/1dsfUzJWDpBIJPrlu3fvGimK8RgOLYvdcAB42bZtG4IgH3/8cXd393Re +77p06dKpU6fYbHZCQgIMLIovqh//VG9/TMlYOUCpVOqXqfj7NCw/nETgi0aj7dy5U6PRvPLKKzU1 +NYODg8/6DSiK5ubmJiQkIAhy6NAha2trCnVYpASqH/9Ub39MyRQvWE4+uQ+wNCiK/ulPfyouLm5q +aoqNjX3vvfeCgoLG3W2ora3FFnJzc5cuXXr79m3DrT/99NPp06cRBBEKhe+++67hePEAjAPtz+Tg +JXtAgOHh4by8vFdeeeXevXupqamT7JmZmfmkLn1CoTAvL6+1tZWKJ6oAkISxcoDhad3ixYuNFMV4 +DMtfXl5OYEnMlUwmu3bt2q1bt1599dXo6OilS5dO8R9+8803YrE4Ly8vNja2tbXVsAcIwAvVj3+q +tz+mZKwcQPV6p+JxTzkdHR3YiL6xsbE7duyY4r8aHBz8+uuvIyIiamtrKddfhSqofvxTvf0xJbgX +BIiEPRCm0+lDQ0M+Pj5P3V+r1WKTwWKDSBu9fACYO2PlgOLiYv0yNqgLtURHR+uXy8rKCCyJhaDT +6S4uLk/dDW79mwbVj3+qtz+mBIPuAgCA5YIcAAAAlgueBwDiFRYWuru7X79+/al70mi0kpISExQJ +AAsBOQAQr7y8/Jk6onC5XOMVBgCLYqx7QagBI4UwKqqXnyo4HI5CoUCf0cDAANEFN3NUP/6pXn5T +gvcDJkb1/tEUUldXR3QRwHhUP/6p3v6YEjwTBgAAywU5AAAALBfkAAAAsFzjnwcEBgbSaDQEQYaH +h7u7u/WD+trZ2d25c0e/1dHRsb6+3kiTN+l0ulmzZrHZbP0Awr9aT0+Ph4eHiSca7O/vHxoa4vP5 +HA7HlHHJqa2tTSAQmPjRHCFBSaivr08mk/H5fAcHB+NF0Q/cFBUVpVarW1pasCFAIiIitFqtQqFo +ampCEGTOnDlsNvuZZqQZGxubMWNGW1ubkUqOIUOjR6DxOeDkyZPYQmpq6tWrV69cudLd3Y0gSHp6 ++p07d3p7ey9fvowgyIcffujv73/v3j28yqHRaMLDw21sbCoqKoKCgv72t7/l5eV5eXmdP38+IiLC +ycnp1q1bUx8HHOsPYGVlRafTi4uL4+LiRkdH8Srq5DQaTVJSEoqiTU1NJ0+eFAgEpolLQgqF4sGD +B0lJSdHR0bt37+ZyuSZolAkJSk4qlaq4uHjv3r2lpaWdnZ0zZ840UqBdu3ZhC/fu3VOr1QcOHMBa +7XPnzgkEgqKion/9618Ignz44Yevv/66XC5/6hf6+vqKxWIajRYREeHh4WFlZfXw4UMjFR4hrtEj +ifH3gj7++OPIyEgWi1VSUuLk5KRUKlksFovFysjIcHJyamhooNPphw4dmj9//lT+llMXGhqalJQU +EhISHx8vlUozMjLUarVcLg8NDf3Xv/7F4/G2bNkyxa9CUfTBgwft7e0IgtDp9Ndff91wYjxj6+/v +r6qqyszMfPnll69fv85isUwWmmysra29vb0ZDIa3t7ejoyODwTDXoOTU19dnZ2fn7u6+bt264uJi +GxsbIwU6c+bMCy+8wGKxjhw5kpOTI5fLsUbjxIkTOTk5+fn5dDr9n//859q1a6dyEYCiqI+Pz+rV +q62srFQq1ZYtW55//nkjlRxDVKNHEuOvAwoKCry9vUtKSh49ejQ4OPjhhx+O2+HChQuLFi1KSkrC +qwRyudzKyqq3tzc9PX1sbKy7u1sqlT569Mjd3f3ChQsCgeAf//jHsmXLbt++PTAwYGdnZ239lP6s +Wq2Ww+FERESIxWIbGxu5XN7Z2cnj8fAq8OT4fP769etDQ0NRFC0qKrLkMc5YLNa9e/d27Nhx584d +Op2uUqnMNSg5eXh4XLp0KTk5uaKiYu/evcarioCAgPnz5xcVFWm1Wo1Gk5ycPG6Ho0ePrl27Vi6X +G07x+CTYddumTZs4HE5fX9/IyAh2Vm48pm/0SGWC9nTGjBlFRUV3796tqakZt4lGo7FYLDabjVd4 +gUCwcOFChUJRX19/7ty5+Ph47CrSxsYGO2SrqqrWrl3b0NCQlZV1+PDhtra21tbWn3/+eZLv7Ozs +1Ol03t7e3t7e2CfNzc137tyZN28eXsWeRGNj49WrV7lcrlwuf/fdd0+dOmWCoOTR3d3d0dGRm5vb +29sbExOjUCg++OCDlStX7t27l81md3R0PP/881u3bnVycqJ6UJLTarXl5eVff/011qQ2NjZmZ2dj +97WNYdGiRUVFRcHBwUNDQ49v7ejo4PP5UzyJHh0dlUql3t7eu3fvxj6JjIwcGRkx6k0YUzZ6ZDNB +DqisrHz48OGKFStWrVo1blNdXV1cXFxGRgZe4e3s7FAUZTAYAQEBd+/epdFo8+fPx65bsVnCNRqN +t7d3c3NzbGwsgiAsFis2NnbyHNDd3b1t27bW1lYvLy86nY4giFAoLCkpKSsrMxwR1xj6+vr27duX +l5cXHx8/ODj4zjvvvPrqq5cuXTK/50gT0ul0O3funD179o4dOxwdHYuLi3NycpYsWVJeXj44OLhh +wwYfH5/a2trNmzevWrUKrzNTQoKSX2tr6969exEEuXXr1tmzZz/77LPVq1ffvHlToVAYI5yXl9cX +X3yB/UjHqaur27hxo0QimeJXBQUF7du3r6Ghwc/PD2t5g4OD29vbURQ13sTRpmz0yGaCHKDT6ezs +7CZ8glRWVmZra3v//n28wtfX1/v6+ra1tTU2NjIYDIFAgA0DoL+NGx8fz2Kxurq6qqqq5s+f7+np ++aTZZTH9/f0cDicpKSkyMlL/CMHe3j4mJqaxsVEul2MzkBhJamrqwYMH4+PjEQThcrnp6emenp5i +sZjP5xsvKEkoFIqEhITf/e53u3btsrKyYrFYoaGhr7zyilAojI2NLSgo6Ovr43K5ERER69atW7Vq +VVhYmFAonOZ834QEpYSPP/54y5Yt1tbWbm5uv/3tb4eGhqqrqysqKubMmWOkiKGhoU9qNEJCQjo7 +O6fyJSqVytfXNykpic1mf/755/pvVigUDx8+jI2N/eGHH/As9P8yZaNHNhPfW0dRdMJT5u+//x73 +G9xXr16Nj4+3sbGprKzUd+Kk0+kMBkOlUrm4uLDZ7NHR0ejo6BUrVvzxj3+c/LHSwMAAh8PZvHlz +UFCQ4ZWvo6Pjtm3bdu3alZSUZLw+Qg0NDQcPHtSvMpnMgIAAsVjs5uZm9r1TsGudN99807BTLPbw +ZubMmf39/cHBwVZWVgiCuLm5ffrpp++8887GjRu7urooF5QSxGLxmTNntFqtVCrlcrl/+tOfCgsL +Ozo65s6da6Su0jQa7UmNxtQPfrVa7evrGxMT4+/vj13EY6KjoxUKRUpKip+fn1gsxqfE/58pGz1S +IcU7YkqlEptDamxsjMVicblcOp3OZDJnzZrl7+/P4/Hu3bsXHh5+7969p/Yr6O/vd3BwWLBgweM3 +f11dXXfv3v3jjz8a67+BIJGRkQUFBfrV3t7eurq62bNnGy8ieVRXVy9btsza2trwrQj99RyTycTa +YgRBrKyswsPDOzo6pp8XCQlKCUKhcO7cueHh4SwWq7q6Ojw8/K9//WtUVBTJr4FUKpWjo+OKFSuC +goLGbVq6dGlsbKy7uzshBTNjpMgB9+7dGx0dnTt37tjYmJeXV3h4OIfDodPpWCYYHR1dtmxZYGBg +YWHhU78qKCho3I0XwzEmFy5ceOvWLXt7e/z/DwiCIMj+/fuPHTuWnJzc0NBw48aN5cuXh4WFRURE +WEi7g6LoFB+dYS0RLr0VCQlKfgcPHmQymQiCfPrpp9nZ2QiC/OY3v4mMjCR5DliwYMG4V2oMf78J +CQlVVVXG7ipqaUiRAwYGBphMpouLi0qlEgqFIyMjtra2VlZWjo6OIyMj1dXVISEhISEhkz8Kxty/ +f9/wvBtF0cOHD+uPexsbm+XLlxupq5lEIrly5YpGo0lPT9+1a9d77713//79lpYWC5nzJCIi4vLl +y8PDw1PZ+eLFi/Pnz59+MiYkKCXQ6fS//vWv77///vHjx48fP+7j4/P5558b9U0rXKjV6sd/v/rH ++B4eHqGhocZ7pGGZSJEDEASRSqWdnZ1isZjH43V3d9vb20ulUl9f3wcPHnR1dXV3d1dUVEzlbFqh +UFy9ehV7ACUSiTIzM2fPnm14O++FF14oLCw0vNWIi5qamrfffrulpaW8vLy9vb2kpOTu3bsSiSQx +MXH79u3YnVl8I5KNp6cnl8s9evToUwf3Ly0t/fvf/75nzx6ZTEbFoFRRUVGRkpLCZrPnzZuXmJhY +WFhI/vvaTCbz4sWL2ANY7Pfr5eVl+MeNiYmprq42as8OSzPxM2GVSvXVV189/vmdO3ciIyONUY6R +kRGRSMRgMPh8vk6nq62tlUgkQ0ND/v7+f/zjH0tKSqaSAFAUFYlEdDo9KipKq9VeuXKFTqe/9tpr +hi/rCgSCwcFBLpcrlUrxKnxPT09KSkpCQsKhQ4cMP3dwcNizZ8+qVatiY2M9PT0XLVqEV0QSGhsb +y87OjouLKy4uXrBggeHf69q1awiCYI/iR0ZGLl26tHXr1s2bN0/lwo6EQalCIpHIZDJnZ2cEQTo6 +Ojw9PWk0mvFuSyqVyic1Gq+//vpUvgFF0cHBwe+//97NzS0oKOibb77p7e2Nj493dXXV7yMUCjMz +MyMiIoqLi3Eq+H+ZvtEjiQlywOjo6IMHD958883HNy1ZsuTixYtGKkpUVNTKlSs1Go1hD27sWV9x +cfFUjl0ajbZ3796CgoKLFy8qlUp3d/cVK1aMezzAZDJdXV3xfeOjsrIyJCTk8dcjMf7+/jk5OUKh +sKamxmTDFhGlsrJy27ZtCoVC380Oe+tS/2w2PT396NGju3btam5uxquDCiFBSU6tVjMYjGvXrmE9 +pC9duvTZZ58ZLwF0dXXdvn17//79j29asmRJWlraxo0bn/olNBrtpZdeUiqVN2/eLC8v7+npWbx4 +8fLlyw334fF4bDY7LCwM3xxAVKNHBhPkAIFAsH379pdeeunxTS+99NKyZcuWLl06lcezz6qnp+dJ +Xzv1Y3dkZCQ2NpZGo0kkkoULF0548zcwMBDHRwIymSw1NfXmzZuTvIQZExOzZMmSrKysTZs2kfyh +3HTodDqZTBYTExMbGztnzpwJK+TWrVtxcXE///wzXjfHCAlKfunp6W5ubps3b8ZW09LSdu/enZGR +YaQUKBKJ9u3bN0mj8bvf/W4qr6fl5uba2tr6+/uPjY394Q9/CA0NfXwfFxcXfXbHC1GNHhlMkAPU +avXY2FhaWtqE/4DH47344ovGqI6urq4nXXM9059cq9V2dXXFxcU96aYhn8/H3iL+NaV8jK2trYOD +w1PfQF69enV2dvabb77Z39+PS1zSYjAYVlZWfD5/wgRsZWWFoijubTEhQUmrsbExLy+vrKxMnxFj +YmIWLVqUk5OzYcMGY0Rsa2tbuXJla2vrhFvnzp079WEqFAqFUql8++23g4ODJ9yBx+N1dHRYWVnh +eDpFVKNHBuNzQHV1NXasPOnUe2Rk5MSJE21tbT4+PvgWJTw8/I033phw0/Xr15/pqwYHByd5amRv +b9/S0hIQEIANRzFNUqmURqMdOHBg8t2ampra2trs7e3NPgcAYqnV6qysrA8//DAwMNDw88OHD4eG +hi5btgz3uQR+//vfL1y48Msvv+RyuRPuoFKpUlJSHB0dt27dOpUv5HK5T0oACII4ODj09PTw+Xy8 +3vUjsNEjHPp4DuDxeNgt+CflbTs7u5GRkfXr1yuVSnyH78jJyamqqppw07N246mvr8/KysKWsb+r +/q+LoqhKpZLJZAwGA5ccYG1tLZFIPvnkk6fuGRQU9NRBT83Dli1b2Gz2hL+op3bgoVZQEjp58uT3 +338/OjqKjXpvSKfTbd68+bvvvsO3g5BarbayshodHZ3kcReKonw+387Obiojx/X19U3y++3q6hod +HXV2dsYrBxDY6BEPRcc3SZcvX57K6E7e3t760TxwIZfLKyoqnlS/zz33nP6YmIo///nPHh4eT9qK +DTDw6NGjZy3khAQCgVarncplKZ1O7+npwSUome3atSslJWWSHZRKZV1dnRkEJafExMQJ+7foDQwM +/PLLLzhGLC8vn8pIfBwOB5vV46mWL18+SVcibKyI9PT0qZdwckQ1eiQxPgfs27dvinfu8D2VePjw +4Zo1a/D6tvb29ikebdOnUCgqKytNE4sSenp6TJ/qCAlKTjKZrLy83JQRRSLRFPecYueOL7/88ssv +v5xGiZ4NUY0eSYzPARbScw4AgBeqD4Vi4Y0eWd4TBgAAYHqQAwAAwHJBDgAAAAuFQg4AAADLhaL/ +A1vHTOVXO1uPAAAAAElFTkSuQmCC +" + height="405.237" + width="408.4216" /> + <text + xml:space="preserve" + style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Monospace;-inkscape-font-specification:Monospace" + x="83.842667" + y="78.575134" + id="text3449" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3451" + x="83.842667" + y="78.575134" + style="font-size:18px">What is the probability that white will win in 3 moves?</tspan></text> + </g> +</svg>