summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbd-912 <bdunahu@gmail.com>2023-11-12 20:10:57 -0700
committerbd-912 <bdunahu@gmail.com>2023-11-12 20:26:49 -0700
commita2b56742da7b30afa00f33c9a806fa6031be68a5 (patch)
tree94acd653183c0cc57e0434f39f5d3917eb99fdc0
parentfa75138690814ad7a06194883a12f25c3936a15e (diff)
Added initial files
-rw-r--r--GameEngine/GoalCollection.py33
-rw-r--r--GameEngine/PlayersCollection.py158
-rw-r--r--GameEngine/__init__.py0
-rw-r--r--GameEngine/multiplayer.py170
-rw-r--r--QNetwork/__init__.py0
-rw-r--r--QNetwork/neuralnetwork_regression.py332
-rw-r--r--QNetwork/optimizers.py116
-rw-r--r--QTable/__init__.py0
-rwxr-xr-xQTable/qtsnake.py102
-rw-r--r--inferior_qt.npybin0 -> 384 bytes
-rw-r--r--revised_snake_q_network.ipynb591
-rw-r--r--revised_snake_q_table.ipynb743
-rw-r--r--revised_snake_q_table_noise.ipynb57
-rw-r--r--superior_qt.npybin0 -> 384 bytes
14 files changed, 2302 insertions, 0 deletions
diff --git a/GameEngine/GoalCollection.py b/GameEngine/GoalCollection.py
new file mode 100644
index 0000000..17e9721
--- /dev/null
+++ b/GameEngine/GoalCollection.py
@@ -0,0 +1,33 @@
+#+AUTHOR: bdunahu
+#+TITLE: multiplayer.py
+#+DESCRIPTION: goal object for multiagent snake
+
+import pygame as pg
+from random import randint
+from collections import namedtuple
+
+Point = namedtuple('Point', 'x, y')
+GREEN = (0,128,43)
+
+class Goal():
+ def __init__(self, display, window_width=640, window_height=480, game_units=40):
+ ''' create initial location '''
+ self.location = None
+
+ self.display = display
+ self.window_width = window_width
+ self.window_height = window_height
+ self.game_units = game_units
+
+ def reset(self, hazards=[]):
+ ''' generate new coordinates for goal '''
+ x = randint(0, (self.window_width-self.game_units )//self.game_units )*self.game_units
+ y = randint(0, (self.window_height-self.game_units )//self.game_units )*self.game_units
+ self.location = Point(x, y)
+ if self.location in hazards:
+ self.reset(hazards)
+
+ def draw(self):
+ ''' draw rectangle directly on field '''
+ pg.draw.rect(self.display, GREEN, pg.Rect(self.location.x, self.location.y,
+ self.game_units, self.game_units))
diff --git a/GameEngine/PlayersCollection.py b/GameEngine/PlayersCollection.py
new file mode 100644
index 0000000..10fa990
--- /dev/null
+++ b/GameEngine/PlayersCollection.py
@@ -0,0 +1,158 @@
+#+AUTHOR: bdunahu
+#+TITLE: PlayersCollection.py
+#+DESCRIPTION: methods for handling and querying snake objects
+
+import pygame as pg
+from random import randint
+from collections import namedtuple
+from enum import Enum
+
+class Direction(Enum):
+ UP = 0
+ RIGHT = 1
+ DOWN = 2
+ LEFT = 3
+
+Point = namedtuple('Point', 'x, y')
+
+YELLOW = (255,255,0)
+RED = (255,0,0)
+PURPLE = (204,51,255)
+WHITE = (255,255,255)
+PLAYER_COLOR = [YELLOW, RED, PURPLE, WHITE]
+
+WINDOW_WIDTH = 640
+WINDOW_HEIGHT = 480
+GAME_UNITS = 40
+DISPLAY = None
+
+class Players():
+ def __init__(self, snake_size, num_players, display, window_width=640, window_height=480, game_units=40):
+ ''' define array list of new Snake objects '''
+ global WINDOW_WIDTH
+ global WINDOW_HEIGHT
+ global GAME_UNITS
+ global DISPLAY
+
+ WINDOW_WIDTH = window_width
+ WINDOW_HEIGHT = window_height
+ GAME_UNITS = game_units
+ DISPLAY = display
+
+ self._index = 0
+ self.num_players = num_players
+
+ self.players = [Snake(snake_size, player_id)
+ for player_id in range(num_players)]
+
+ def __iter__(self):
+ return iter(self.players)
+
+ def __getitem__(self, index):
+ return self.players[index]
+
+ def full_reset(self):
+ ''' reset every snake position '''
+ # map(lambda player:player.reset(), self.players)
+ for player in self.players:
+ player.reset()
+
+ def move_all(self):
+ ''' move all snakes '''
+ # map(lambda player:player.move(), self.players)
+ for player in self.players:
+ player.move()
+
+ def reward_killer(self, player):
+ ''' split play length up against killing snakes '''
+ killer = self._point_lookup(player.head)
+ if not killer == None and not killer == player:
+ rewards = player.score.curr_score + player.size
+ killer.deficit += rewards
+ killer.score.curr_score += rewards
+ killer.score.total_score += rewards
+
+ def _point_lookup(self, head):
+ for player in self.players:
+ if head in player.snake:
+ return player
+ return None
+
+ def draw(self):
+ ''' draw all snakes '''
+ # map(lambda player:player.draw(), self.players)
+ for player in self.players:
+ player.draw()
+
+class Snake():
+ def __init__(self, initial_size, player_id):
+ ''' define initial size (length), direction, and position '''
+ self.player_id = player_id
+ self.size = initial_size
+ self.direction = None
+ self.head = None
+ self.snake = []
+ self.score = Score(self.player_id)
+ self.deficit = 0 # for how many moves does this snake need to grow?
+
+ def reset(self):
+ self.score.reset()
+ self.deficit = 0
+ self.direction = Direction.RIGHT.value
+ x = randint(0, (WINDOW_WIDTH-GAME_UNITS )//GAME_UNITS )*GAME_UNITS
+ y = randint(0, (WINDOW_HEIGHT-GAME_UNITS )//GAME_UNITS )*GAME_UNITS
+ self.head = Point(x,y)
+ self.snake = [self.head]
+ for seg in range(self.size-1):
+ self.snake.append(Point(self.head.x-(seg*GAME_UNITS), self.head.y))
+
+ def move(self):
+ ''' update snake coordinates by inserting new head '''
+ x = self.head.x
+ y = self.head.y
+ if self.direction == Direction.RIGHT.value:
+ x += GAME_UNITS
+ if self.direction == Direction.LEFT.value:
+ x -= GAME_UNITS
+ if self.direction == Direction.DOWN.value:
+ y += GAME_UNITS
+ if self.direction == Direction.UP.value:
+ y -= GAME_UNITS
+
+ self.head = Point(x, y)
+ self.snake.insert(0,self.head)
+
+ def in_wall(self):
+ return True if (self.head.x > WINDOW_WIDTH - GAME_UNITS or
+ self.head.x < 0 or
+ self.head.y > WINDOW_HEIGHT - GAME_UNITS or
+ self.head.y < 0) else False
+
+ def draw(self):
+ ''' draw rectangle(s) directly on field '''
+ for seg in self.snake: # see explanation in engine.org
+ pg.draw.rect(DISPLAY, PLAYER_COLOR[self.player_id], pg.Rect(seg.x, seg.y,
+ GAME_UNITS, GAME_UNITS))
+ self.score.draw()
+
+class Score():
+ def __init__(self, player_id):
+ ''' initialize score counter '''
+ self.player_id = player_id
+ self.font = pg.font.SysFont("monospace", 16)
+ self.curr_score = 0
+ self.total_score = 0
+ self.deaths = 0
+ self.kills = 0
+
+ def scored(self):
+ self.curr_score += 1
+ self.total_score += 1
+
+ def reset(self):
+ self.curr_score = 0
+
+ def draw(self):
+ ''' draw score on top left '''
+ score_surf = self.font.render(f'Current: {self.curr_score} Total: {self.total_score}', True, PLAYER_COLOR[self.player_id])
+ DISPLAY.blit(score_surf, (0, 0+24*self.player_id))
diff --git a/GameEngine/__init__.py b/GameEngine/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/GameEngine/__init__.py
diff --git a/GameEngine/multiplayer.py b/GameEngine/multiplayer.py
new file mode 100644
index 0000000..9f6b651
--- /dev/null
+++ b/GameEngine/multiplayer.py
@@ -0,0 +1,170 @@
+#+AUTHOR: bdunahu
+#+TITLE: multiplayer.py
+#+DESCRIPTION game engine for multiagent snake
+
+from enum import Enum
+import pygame as pg
+import numpy as np
+
+from GameEngine import PlayersCollection
+from GameEngine import GoalCollection
+
+BLACK = (0, 0, 0)
+
+class CollisionType(Enum): # each of these means different outcome for model
+ DEATH = 0
+ GOAL = 1
+ NONE = 2
+
+
+class Playfield:
+ def __init__(self, window_width=640, window_height=480, units=40, g_speed=25, s_size=3):
+ ''' initialize pygame modules, snake, goal, and score objects '''
+ global DISPLAY
+
+ self._g_speed = g_speed # game speed
+ self._s_size = s_size # initial snake size
+ self._units = units
+ self._window_width = window_width
+ self._window_height = window_height
+ self._player_count = -1 # number of registered players
+ ''' for human feedback '''
+ self._game_state = False # false is game over
+ self._draw_on = True
+ self._clock = pg.time.Clock()
+
+ ''' objects '''
+ pg.init()
+ self.display = pg.display.set_mode(
+ [self._window_width, self._window_height],
+ pg.HWSURFACE) # display object (see explanation in engine.org)
+ self._players = None
+ self._goal = None
+
+ def add_player(self):
+ '''
+ Returns the player's number to the callee.
+ If the player count is over four, returns None
+ '''
+ if self._player_count < 4 or self._game_state == True:
+ self._player_count += 1
+ return self._player_count
+ return None
+
+ def get_heads_tails_and_goal(self):
+ '''
+ Returns an array of heads, an array of tail positions,
+ and the goal position
+ '''
+ heads = []
+ for player in self._players.players:
+ heads.append(player.head)
+ return heads, self._get_player_bodies(), self._goal.location
+
+ def get_viable_actions(self, player_id):
+ '''
+ Given a player's id,
+ returns a list of actions that does
+ not result in immediate death
+ '''
+ head = self._players.players[player_id].head
+ tail = self._players.players[player_id].snake
+ danger_array = np.array([
+ head.y-self._units < 0 or PlayersCollection.Point(head.x, head.y-self._units) in tail[1:], # up
+ head.x+self._units >= self._window_width or PlayersCollection.Point(head.x+self._units, head.y) in tail[1:], # right
+ head.y+self._units >= self._window_height or PlayersCollection.Point(head.x, head.y+self._units) in tail[1:], # down
+ head.x-self._units < 0 or PlayersCollection.Point(head.x-self._units, head.y) in tail[1:], # left
+ ])
+
+ return np.where(danger_array == False)[0]
+
+ def start_game(self):
+ '''
+ Initializes player objects, starts the game
+ '''
+ self._players = PlayersCollection.Players(self._s_size, self._player_count+1, self.display, game_units=self._units)
+ self._goal = GoalCollection.Goal(self.display, game_units=self._units)
+ self._reset()
+ print(f'Game starting with {self._player_count+1} players.')
+
+ def stop_game(self):
+ '''
+ Restarts the game, allows adding/removing players
+ '''
+ self._game_state = False
+ print(f'Game over!')
+
+ def cleanup(self):
+ ''' end game session '''
+ pg.quit()
+
+ def player_advance(self, actions, noise=0.0):
+ ''' given a list of snake actions '''
+ ''' return a list of results, and '''
+ ''' update game state '''
+
+ for player_id, action in enumerate(actions):
+ if np.random.uniform < noise:
+ # random action (noise)
+ random_choice = [0, 1, 2, 3]
+ random_choice.remove(self._players[player_id].direction)
+ self._players[player_id].direction = np.random.choice(random_choice)
+ else:
+ self._players[player_id].direction = action
+
+ self._players.move_all()
+ collisions = self._check_player_collisions()
+ if self._draw_on:
+ self._update_ui()
+ return collisions
+
+ def toggle_draw(self):
+ ''' turns off and on UI '''
+ self._draw_on = not self._draw_on
+ print(f'Draw is now {self._draw_on}.')
+
+ def _check_player_collisions(self):
+ results = []
+ for player in self._players:
+ ''' determine what obstacle was hit '''
+ hazards = self._get_player_bodies()
+ hazards.remove(player.head)
+ if (player.head in hazards or player.in_wall()):
+ self._players.reward_killer(player)
+ player.reset()
+ results.append(CollisionType.DEATH)
+ elif player.head == self._goal.location:
+ player.score.curr_score += 1
+ player.score.total_score += 1
+ self._goal.reset(hazards)
+ results.append(CollisionType.GOAL)
+ else:
+ if player.deficit <= 0:
+ player.snake.pop()
+ else:
+ player.deficit -= 1
+ results.append(CollisionType.NONE)
+ return results
+
+ def _get_player_bodies(self):
+ ''' return an array of all tail coordinates '''
+ tails = [0]
+ for player in self._players:
+ tails += player.snake
+ return tails
+
+ def _update_ui(self):
+ ''' flush new positions to screen '''
+ self.display.fill(BLACK)
+ self._players.draw()
+ self._goal.draw()
+
+ pg.display.flip() # full screen update
+ self._clock.tick(self._g_speed)
+
+ def _reset(self):
+ ''' reset game state '''
+ ''' required by training '''
+ self._game_state = True # false is game over
+ self._goal.reset()
+ self._players.full_reset()
diff --git a/QNetwork/__init__.py b/QNetwork/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/QNetwork/__init__.py
diff --git a/QNetwork/neuralnetwork_regression.py b/QNetwork/neuralnetwork_regression.py
new file mode 100644
index 0000000..b26be5e
--- /dev/null
+++ b/QNetwork/neuralnetwork_regression.py
@@ -0,0 +1,332 @@
+import numpy as np
+from QNetwork import optimizers
+import sys # for sys.float_info.epsilon
+import matplotlib.pyplot as plt
+import matplotlib.patches as pltpatch # for Arc
+import matplotlib.collections as pltcoll
+import math
+
+######################################################################
+## class NeuralNetwork()
+######################################################################
+
+class NeuralNetwork():
+
+
+ def __init__(self, n_inputs, n_hiddens_per_layer, n_outputs, activation_function='tanh'):
+ self.n_inputs = n_inputs
+ self.n_outputs = n_outputs
+ self.activation_function = activation_function
+
+ # Set self.n_hiddens_per_layer to [] if argument is 0, [], or [0]
+ if n_hiddens_per_layer == 0 or n_hiddens_per_layer == [] or n_hiddens_per_layer == [0]:
+ self.n_hiddens_per_layer = []
+ else:
+ self.n_hiddens_per_layer = n_hiddens_per_layer
+
+ # Initialize weights, by first building list of all weight matrix shapes.
+ n_in = n_inputs
+ shapes = []
+ for nh in self.n_hiddens_per_layer:
+ shapes.append((n_in + 1, nh))
+ n_in = nh
+ shapes.append((n_in + 1, n_outputs))
+
+ # self.all_weights: vector of all weights
+ # self.Ws: list of weight matrices by layer
+ self.all_weights, self.Ws = self.make_weights_and_views(shapes)
+
+ # Define arrays to hold gradient values.
+ # One array for each W array with same shape.
+ self.all_gradients, self.dE_dWs = self.make_weights_and_views(shapes)
+
+ self.trained = False
+ self.total_epochs = 0
+ self.error_trace = []
+ self.Xmeans = None
+ self.Xstds = None
+ self.Tmeans = None
+ self.Tstds = None
+
+ def setup_standardization(self, Xmeans, Xstds, Tmeans, Tstds):
+ self.Xmeans = np.array(Xmeans)
+ self.Xstds = np.array(Xstds)
+ self.Tmeans = np.array(Tmeans)
+ self.Tstds = np.array(Tstds)
+
+ def make_weights_and_views(self, shapes):
+ # vector of all weights built by horizontally stacking flatenned matrices
+ # for each layer initialized with uniformly-distributed values.
+ all_weights = np.hstack([np.random.uniform(-1, 1, size=shape).flat / np.sqrt(shape[0])
+ for shape in shapes])
+ # Build list of views by reshaping corresponding elements from vector of all weights
+ # into correct shape for each layer.
+ views = []
+ start = 0
+ for shape in shapes:
+ size =shape[0] * shape[1]
+ views.append(all_weights[start:start + size].reshape(shape))
+ start += size
+ return all_weights, views
+
+
+ # Return string that shows how the constructor was called
+ def __repr__(self):
+ return f'{type(self).__name__}({self.n_inputs}, {self.n_hiddens_per_layer}, {self.n_outputs}, \'{self.activation_function}\')'
+
+
+ # Return string that is more informative to the user about the state of this neural network.
+ def __str__(self):
+ result = self.__repr__()
+ if len(self.error_trace) > 0:
+ return self.__repr__() + f' trained for {len(self.error_trace)} epochs, final training error {self.error_trace[-1]:.4f}'
+
+
+ def train(self, X, T, n_epochs, learning_rate, method='sgd', verbose=True):
+ '''
+train:
+ X: n_samples x n_inputs matrix of input samples, one per row
+ T: n_samples x n_outputs matrix of target output values, one sample per row
+ n_epochs: number of passes to take through all samples updating weights each pass
+ learning_rate: factor controlling the step size of each update
+ method: is either 'sgd' or 'adam'
+ '''
+
+ # Setup standardization parameters
+ if self.Xmeans is None:
+ self.Xmeans = X.mean(axis=0)
+ self.Xstds = X.std(axis=0)
+ self.Xstds[self.Xstds == 0] = 1 # So we don't divide by zero when standardizing
+ self.Tmeans = T.mean(axis=0)
+ self.Tstds = T.std(axis=0)
+
+ # Standardize X and T
+ X = (X - self.Xmeans) / self.Xstds
+ T = (T - self.Tmeans) / self.Tstds
+
+ # Instantiate Optimizers object by giving it vector of all weights
+ optimizer = optimizers.Optimizers(self.all_weights)
+
+ # Define function to convert value from error_f into error in original T units,
+ # but only if the network has a single output. Multiplying by self.Tstds for
+ # multiple outputs does not correctly unstandardize the error.
+ if len(self.Tstds) == 1:
+ error_convert_f = lambda err: (np.sqrt(err) * self.Tstds)[0] # to scalar
+ else:
+ error_convert_f = lambda err: np.sqrt(err)[0] # to scalar
+
+
+ if method == 'sgd':
+
+ error_trace = optimizer.sgd(self.error_f, self.gradient_f,
+ fargs=[X, T], n_epochs=n_epochs,
+ learning_rate=learning_rate,
+ verbose=verbose,
+ error_convert_f=error_convert_f)
+
+ elif method == 'adam':
+
+ error_trace = optimizer.adam(self.error_f, self.gradient_f,
+ fargs=[X, T], n_epochs=n_epochs,
+ learning_rate=learning_rate,
+ verbose=verbose,
+ error_convert_f=error_convert_f)
+
+ else:
+ raise Exception("method must be 'sgd' or 'adam'")
+
+ self.error_trace = error_trace
+
+ # Return neural network object to allow applying other methods after training.
+ # Example: Y = nnet.train(X, T, 100, 0.01).use(X)
+ return self
+
+ def relu(self, s):
+ s[s < 0] = 0
+ return s
+
+ def grad_relu(self, s):
+ return (s > 0).astype(int)
+
+ def forward_pass(self, X):
+ '''X assumed already standardized. Output returned as standardized.'''
+ self.Ys = [X]
+ for W in self.Ws[:-1]:
+ if self.activation_function == 'relu':
+ self.Ys.append(self.relu(self.Ys[-1] @ W[1:, :] + W[0:1, :]))
+ else:
+ self.Ys.append(np.tanh(self.Ys[-1] @ W[1:, :] + W[0:1, :]))
+ last_W = self.Ws[-1]
+ self.Ys.append(self.Ys[-1] @ last_W[1:, :] + last_W[0:1, :])
+ return self.Ys
+
+ # Function to be minimized by optimizer method, mean squared error
+ def error_f(self, X, T):
+ Ys = self.forward_pass(X)
+ mean_sq_error = np.mean((T - Ys[-1]) ** 2)
+ return mean_sq_error
+
+ # Gradient of function to be minimized for use by optimizer method
+ def gradient_f(self, X, T):
+ '''Assumes forward_pass just called with layer outputs in self.Ys.'''
+ error = T - self.Ys[-1]
+ n_samples = X.shape[0]
+ n_outputs = T.shape[1]
+ delta = - error / (n_samples * n_outputs)
+ n_layers = len(self.n_hiddens_per_layer) + 1
+ # Step backwards through the layers to back-propagate the error (delta)
+ for layeri in range(n_layers - 1, -1, -1):
+ # gradient of all but bias weights
+ self.dE_dWs[layeri][1:, :] = self.Ys[layeri].T @ delta
+ # gradient of just the bias weights
+ self.dE_dWs[layeri][0:1, :] = np.sum(delta, 0)
+ # Back-propagate this layer's delta to previous layer
+ if self.activation_function == 'relu':
+ delta = delta @ self.Ws[layeri][1:, :].T * self.grad_relu(self.Ys[layeri])
+ else:
+ delta = delta @ self.Ws[layeri][1:, :].T * (1 - self.Ys[layeri] ** 2)
+ return self.all_gradients
+
+ def use(self, X):
+ '''X assumed to not be standardized'''
+ # Standardize X
+ X = (X - self.Xmeans) / self.Xstds
+ Ys = self.forward_pass(X)
+ Y = Ys[-1]
+ # Unstandardize output Y before returning it
+ return Y * self.Tstds + self.Tmeans
+
+ def draw(self, input_names=None, output_names=None, scale='by layer', gray=False):
+ plt.title('{} weights'.format(sum([Wi.size for Wi in self.Ws])))
+
+ def isOdd(x):
+ return x % 2 != 0
+
+ n_layers = len(self.Ws)
+
+ Wmax_overall = np.max(np.abs(np.hstack([w.reshape(-1) for w in self.Ws])))
+
+ # calculate xlim and ylim for whole network plot
+ # Assume 4 characters fit between each wire
+ # -0.5 is to leave 0.5 spacing before first wire
+ xlim = max(map(len, input_names)) / 4.0 if input_names else 1
+ ylim = 0
+
+ for li in range(n_layers):
+ ni, no = self.Ws[li].shape #no means number outputs this layer
+ if not isOdd(li):
+ ylim += ni + 0.5
+ else:
+ xlim += ni + 0.5
+
+ ni, no = self.Ws[n_layers-1].shape #no means number outputs this layer
+ if isOdd(n_layers):
+ xlim += no + 0.5
+ else:
+ ylim += no + 0.5
+
+ # Add space for output names
+ if output_names:
+ if isOdd(n_layers):
+ ylim += 0.25
+ else:
+ xlim += round(max(map(len, output_names)) / 4.0)
+
+ ax = plt.gca()
+
+ # changes from Jim Jazwiecki (jim.jazwiecki@gmail.com) CS480 student
+ character_width_factor = 0.07
+ padding = 2
+ if input_names:
+ x0 = max([1, max(map(len, input_names)) * (character_width_factor * 3.5)])
+ else:
+ x0 = 1
+ y0 = 0 # to allow for constant input to first layer
+ # First Layer
+ if input_names:
+ y = 0.55
+ for n in input_names:
+ y += 1
+ ax.text(x0 - (character_width_factor * padding), y, n, horizontalalignment="right", fontsize=20)
+
+ patches = []
+ for li in range(n_layers):
+ thisW = self.Ws[li]
+ if scale == 'by layer':
+ maxW = np.max(np.abs(thisW))
+ else:
+ maxW = Wmax_overall
+ ni, no = thisW.shape
+ if not isOdd(li):
+ # Even layer index. Vertical layer. Origin is upper left.
+ # Constant input
+ ax.text(x0 - 0.2, y0 + 0.5, '1', fontsize=20)
+ for i in range(ni):
+ ax.plot((x0, x0 + no - 0.5), (y0 + i + 0.5, y0 + i + 0.5), color='gray')
+ # output lines
+ for i in range(no):
+ ax.plot((x0 + 1 + i - 0.5, x0 + 1 + i - 0.5), (y0, y0 + ni + 1), color='gray')
+ # cell "bodies"
+ xs = x0 + np.arange(no) + 0.5
+ ys = np.array([y0 + ni + 0.5] * no)
+ for x, y in zip(xs, ys):
+ patches.append(pltpatch.RegularPolygon((x, y - 0.4), 3, 0.3, 0, color ='#555555'))
+ # weights
+ if gray:
+ colors = np.array(['black', 'gray'])[(thisW.flat >= 0) + 0]
+ else:
+ colors = np.array(['red', 'green'])[(thisW.flat >= 0) + 0]
+ xs = np.arange(no) + x0 + 0.5
+ ys = np.arange(ni) + y0 + 0.5
+ coords = np.meshgrid(xs, ys)
+ for x, y, w, c in zip(coords[0].flat, coords[1].flat,
+ np.abs(thisW / maxW).flat, colors):
+ patches.append(pltpatch.Rectangle((x - w / 2, y - w / 2), w, w, color=c))
+ y0 += ni + 1
+ x0 += -1 ## shift for next layer's constant input
+ else:
+ # Odd layer index. Horizontal layer. Origin is upper left.
+ # Constant input
+ ax.text(x0 + 0.5, y0 - 0.2, '1', fontsize=20)
+ # input lines
+ for i in range(ni):
+ ax.plot((x0 + i + 0.5, x0 + i + 0.5), (y0, y0 + no - 0.5), color='gray')
+ # output lines
+ for i in range(no):
+ ax.plot((x0, x0 + ni + 1), (y0 + i+ 0.5, y0 + i + 0.5), color='gray')
+ # cell 'bodies'
+ xs = np.array([x0 + ni + 0.5] * no)
+ ys = y0 + 0.5 + np.arange(no)
+ for x, y in zip(xs, ys):
+ patches.append(pltpatch.RegularPolygon((x - 0.4, y), 3, 0.3, -math.pi / 2, color ='#555555'))
+ # weights
+ if gray:
+ colors = np.array(['black', 'gray'])[(thisW.flat >= 0) + 0]
+ else:
+ colors = np.array(['red', 'green'])[(thisW.flat >= 0) + 0]
+ xs = np.arange(ni) + x0 + 0.5
+ ys = np.arange(no) + y0 + 0.5
+ coords = np.meshgrid(xs, ys)
+ for x, y, w, c in zip(coords[0].flat, coords[1].flat,
+ np.abs(thisW / maxW).flat, colors):
+ patches.append(pltpatch.Rectangle((x - w / 2, y - w / 2), w, w, color=c))
+ x0 += ni + 1
+ y0 -= 1 ##shift to allow for next layer's constant input
+
+ collection = pltcoll.PatchCollection(patches, match_original=True)
+ ax.add_collection(collection)
+
+ # Last layer output labels
+ if output_names:
+ if isOdd(n_layers):
+ x = x0 + 1.5
+ for n in output_names:
+ x += 1
+ ax.text(x, y0 + 0.5, n, fontsize=20)
+ else:
+ y = y0 + 0.6
+ for n in output_names:
+ y += 1
+ ax.text(x0 + 0.2, y, n, fontsize=20)
+ ax.axis([0, xlim, ylim, 0])
+ ax.axis('off')
diff --git a/QNetwork/optimizers.py b/QNetwork/optimizers.py
new file mode 100644
index 0000000..7d28f92
--- /dev/null
+++ b/QNetwork/optimizers.py
@@ -0,0 +1,116 @@
+import numpy as np
+
+######################################################################
+## class Optimizers()
+######################################################################
+
+class Optimizers():
+
+ def __init__(self, all_weights):
+ '''all_weights is a vector of all of a neural networks weights concatenated into a one-dimensional vector'''
+
+ self.all_weights = all_weights
+
+ # The following initializations are only used by adam.
+ # Only initializing m, v, beta1t and beta2t here allows multiple calls to adam to handle training
+ # with multiple subsets (batches) of training data.
+ self.mt = np.zeros_like(all_weights)
+ self.vt = np.zeros_like(all_weights)
+ self.beta1 = 0.9
+ self.beta2 = 0.999
+ self.beta1t = 1
+ self.beta2t = 1
+
+
+ def sgd(self, error_f, gradient_f, fargs=[], n_epochs=100, learning_rate=0.001, verbose=True, error_convert_f=None):
+ '''
+error_f: function that requires X and T as arguments (given in fargs) and returns mean squared error.
+gradient_f: function that requires X and T as arguments (in fargs) and returns gradient of mean squared error
+ with respect to each weight.
+error_convert_f: function that converts the standardized error from error_f to original T units.
+ '''
+
+ error_trace = []
+ epochs_per_print = n_epochs // 10
+
+ for epoch in range(n_epochs):
+
+ error = error_f(*fargs)
+ grad = gradient_f(*fargs)
+
+ # Update all weights using -= to modify their values in-place.
+ self.all_weights -= learning_rate * grad
+
+ if error_convert_f:
+ error = error_convert_f(error)
+ error_trace.append(error)
+
+ if verbose and ((epoch + 1) % max(1, epochs_per_print) == 0):
+ print(f'sgd: Epoch {epoch+1:d} Error={error:.5f}')
+
+ return error_trace
+
+ def adam(self, error_f, gradient_f, fargs=[], n_epochs=100, learning_rate=0.001, verbose=True, error_convert_f=None):
+ '''
+error_f: function that requires X and T as arguments (given in fargs) and returns mean squared error.
+gradient_f: function that requires X and T as arguments (in fargs) and returns gradient of mean squared error
+ with respect to each weight.
+error_convert_f: function that converts the standardized error from error_f to original T units.
+ '''
+
+ alpha = learning_rate # learning rate called alpha in original paper on adam
+ epsilon = 1e-8
+ error_trace = []
+ epochs_per_print = n_epochs // 10
+
+ for epoch in range(n_epochs):
+
+ error = error_f(*fargs)
+ grad = gradient_f(*fargs)
+
+ self.mt[:] = self.beta1 * self.mt + (1 - self.beta1) * grad
+ self.vt[:] = self.beta2 * self.vt + (1 - self.beta2) * grad * grad
+ self.beta1t *= self.beta1
+ self.beta2t *= self.beta2
+
+ m_hat = self.mt / (1 - self.beta1t)
+ v_hat = self.vt / (1 - self.beta2t)
+
+ # Update all weights using -= to modify their values in-place.
+ self.all_weights -= alpha * m_hat / (np.sqrt(v_hat) + epsilon)
+
+ if error_convert_f:
+ error = error_convert_f(error)
+ error_trace.append(error)
+
+ if verbose and ((epoch + 1) % max(1, epochs_per_print) == 0):
+ print(f'Adam: Epoch {epoch+1:d} Error={error:.5f}')
+
+ return error_trace
+
+if __name__ == '__main__':
+
+ import matplotlib.pyplot as plt
+ plt.ion()
+
+ def parabola(wmin):
+ return ((w - wmin) ** 2)[0]
+
+ def parabola_gradient(wmin):
+ return 2 * (w - wmin)
+
+ w = np.array([0.0])
+ optimizer = Optimizers(w)
+
+ wmin = 5
+ optimizer.sgd(parabola, parabola_gradient, [wmin],
+ n_epochs=500, learning_rate=0.1)
+
+ print(f'sgd: Minimum of parabola is at {wmin}. Value found is {w}')
+
+ w = np.array([0.0])
+ optimizer = Optimizers(w)
+ optimizer.adam(parabola, parabola_gradient, [wmin],
+ n_epochs=500, learning_rate=0.1)
+
+ print(f'adam: Minimum of parabola is at {wmin}. Value found is {w}')
diff --git a/QTable/__init__.py b/QTable/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/QTable/__init__.py
diff --git a/QTable/qtsnake.py b/QTable/qtsnake.py
new file mode 100755
index 0000000..4b1eac7
--- /dev/null
+++ b/QTable/qtsnake.py
@@ -0,0 +1,102 @@
+#+AUTHOR: bdunahu
+#+TITLE: qtsnake.py
+#+DESCRIPTION qtable lookup, training, and handling for multiagent snake
+
+import numpy as np
+from GameEngine import multiplayer
+from collections import namedtuple
+
+WINDOW_WIDTH = None
+WINDOW_HEIGHT = None
+GAME_UNITS = None
+
+Point = namedtuple('Point', 'x, y')
+
+
+def sense_goal(head, goal):
+ '''
+ maps head and goal location onto an
+ integer corresponding to approx location
+ '''
+ diffs = Point(goal.x - head.x, goal.y - head.y)
+
+ if diffs.x == 0 and diffs.y < 0:
+ return 0
+ if diffs.x > 0 and diffs.y < 0:
+ return 1
+ if diffs.x > 0 and diffs.y == 0:
+ return 2
+ if diffs.x > 0 and diffs.y > 0:
+ return 3
+ if diffs.x == 0 and diffs.y > 0:
+ return 4
+ if diffs.x < 0 and diffs.y > 0:
+ return 5
+ if diffs.x < 0 and diffs.y == 0:
+ return 6
+ return 7
+
+def load_q(filename):
+ ''' loads np array from given file '''
+ if not filename.endswith('.npy'):
+ exit(1)
+ return np.load(filename)
+
+class QSnake:
+ def __init__(self, game_engine):
+ ''' initialize fields required by model '''
+ self.game_engine = game_engine
+
+ def index_actions(self, q, pid):
+ '''
+ given q, player_id, an array of heads,
+ and the goal position,
+ indexes into the corresponding expected
+ reward of each action
+ '''
+ heads, tails, goal = self.game_engine.get_heads_tails_and_goal()
+ state = sense_goal(heads[pid], goal)
+ return state, q[state, :]
+
+ def argmin_gen(self, rewards):
+ '''
+ Given an array of rewards indexed by actions,
+ yields actions in order from most rewarding to
+ least rewarding
+ '''
+ rewards = rewards.copy()
+ for i in range(rewards.size):
+ best_action = np.argmin(rewards)
+ rewards[best_action] = float("inf")
+ yield best_action
+
+ def pick_greedy_action(self, q, pid, epsilon):
+ '''
+ given a q table, the id of the player
+ taking action, and a randomization factor,
+ returns the most rewarding non-lethal action
+ or a non-lethal random action.
+ '''
+ viable_actions = self.game_engine.get_viable_actions(pid)
+ state, rewards = self.index_actions(q, pid)
+
+ if np.random.uniform() < epsilon:
+ return (state, np.random.choice(viable_actions)) if viable_actions.size > 0 else (state, 0)
+ for action in self.argmin_gen(rewards):
+ if action in viable_actions:
+ return (state, action)
+ return (state, 0) # death
+
+ def update_q(self, q, old_state_action, new_state_action, outcome, lr=0.05):
+ '''
+ given a q table, the previous state/action pair,
+ the new state/action pair, the outcome of the last
+ action, and the learning rate
+ updates q with the temporal difference.
+ '''
+ if outcome == multiplayer.CollisionType.GOAL:
+ q[new_state_action[0], new_state_action[1]] = 0
+ else:
+ td_error = -1 + q[new_state_action[0], new_state_action[1]] - q[old_state_action[0], old_state_action[1]]
+ q[old_state_action[0], old_state_action[1]] += lr * td_error
+
diff --git a/inferior_qt.npy b/inferior_qt.npy
new file mode 100644
index 0000000..551a537
--- /dev/null
+++ b/inferior_qt.npy
Binary files differ
diff --git a/revised_snake_q_network.ipynb b/revised_snake_q_network.ipynb
new file mode 100644
index 0000000..40952d7
--- /dev/null
+++ b/revised_snake_q_network.ipynb
@@ -0,0 +1,591 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "73c6d255-0c32-4895-9a22-e95eadb25103",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "pygame 2.5.1 (SDL 2.28.2, Python 3.11.5)\n",
+ "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
+ ]
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "from collections import namedtuple\n",
+ "from IPython.core.debugger import Pdb\n",
+ "from IPython.display import display, clear_output\n",
+ "\n",
+ "from QNetwork import neuralnetwork_regression as nn\n",
+ "from GameEngine import multiplayer\n",
+ "from QTable import qtsnake\n",
+ "\n",
+ "Point = namedtuple('Point', 'x, y')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b3aab739-e016-4700-89c9-41f3c2f536cf",
+ "metadata": {},
+ "source": [
+ "### New Game Implementation\n",
+ "\n",
+ "I have an improved game implementation which allows for multiplayer snake games, as well as simplified training. This notebook will go over training of a simple q-network, which maps a total of 32 different combinations of states and actions onto rewards, much like the previous q-table implementation from ***revised_snake_q_table.ipynb***.\n",
+ "\n",
+ "Please read that notebook first if interested in a more complete description of the new game engine. As usual, we have some game-setup to do:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "682a7036-4f0d-4f3d-b147-6355c0a2f93e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# defines game window size and block size, in pixels\n",
+ "WINDOW_WIDTH = 640\n",
+ "WINDOW_HEIGHT = 480\n",
+ "GAME_UNITS = 80"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "41cfbec9-e14e-4c58-95dd-2e3fb1788e72",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "game_engine = multiplayer.Playfield(window_width=WINDOW_WIDTH,\n",
+ " window_height=WINDOW_HEIGHT,\n",
+ " units=GAME_UNITS,\n",
+ " g_speed=35,\n",
+ " s_size=1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "804a13dc-7dd4-43f0-bc47-e781bc022075",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Game starting with 1 players.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "p1 = game_engine.add_player()\n",
+ "game_engine.start_game()\n",
+ "p1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "34efdb66-7a8e-4b48-a015-d1eb8a029915",
+ "metadata": {},
+ "source": [
+ "Training thousands of steps is a little bit slow with the graphics on. It makes only a small difference here, but it provides little information anyways:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "b94f16d4-65bb-4150-bdc0-6cc648e3cb7e",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Draw is now False.\n"
+ ]
+ }
+ ],
+ "source": [
+ "game_engine.toggle_draw()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "43cefedf-e005-4910-9b4c-953697aa3f26",
+ "metadata": {},
+ "source": [
+ "### State-sensing methods, defining reinforcement and greedy-action selector\n",
+ "\n",
+ "I have also imported the aforementioned q_table implementation as qtsnake. It will come back in the end of the notebook when I pair the q_table and q_network against each other, but to make the game fair, I'll use the exact same state-sensing method:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "71c97804-74d3-4248-bdb7-5519aa02b556",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "<function QTable.qtsnake.sense_goal(head, goal)>"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "qtsnake.sense_goal"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e065f223-9e19-4f21-ba75-8d44fc62d353",
+ "metadata": {},
+ "source": [
+ "Even though I plan to only call it when selecting a greedy_action, I'll wrap it in a neat 'query_state' function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "26b8f8bf-ad08-40f8-847f-88351e262c1d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def query_state(id):\n",
+ " '''\n",
+ " given a player's id,\n",
+ " returns their state\n",
+ " '''\n",
+ " heads, _, goal = game_engine.get_heads_tails_and_goal()\n",
+ " return np.array(qtsnake.sense_goal(heads[id], goal))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7d61e508-0661-4893-a720-f0a511c52809",
+ "metadata": {},
+ "source": [
+ "And a reinforcement function. Because I took the requirement to sense danger away, we only need two outputs from the reinforcement function.\n",
+ "\n",
+ "The output of this function was chosen due to being the best-performing. It is possible the reward for GOAL should be higher or lower. In actuality, the reinforcement for non-goals will never be used. I prefer the simplicity of using the discount factor to force agents to the goal quickly."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "0af0a115-83b9-498a-8228-dc79580131f1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def reinforcement(outcome):\n",
+ " '''\n",
+ " given an outcome of an action,\n",
+ " returns associated reward\n",
+ " '''\n",
+ " if outcome == multiplayer.CollisionType.GOAL:\n",
+ " return -3\n",
+ " return 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "45e6040c-9aae-4f9e-8ef6-cf23b4043622",
+ "metadata": {},
+ "source": [
+ "Here is the first real interesting function. It takes its implementation largely from the marble example, but it accepts and returns parameters as closely to the previous q-table version.\n",
+ "\n",
+ "In essence, I ask the game the viable actions for a player, take into account our current state, and choose the action with the greatest expected reward, or a random action. This is called epsilon greedy selection.\n",
+ "\n",
+ "When calling use on the network, it maps a state and action onto a reward, just the same as indexing the q-table. We return the expected reward for this action in addition, because it is needed later for learning with discounted rewards."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "a76fd63a-478a-43ad-91ce-df1dff03e565",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def pick_greedy_action(q_net, id, epsilon):\n",
+ " '''\n",
+ " given a q network, the id of the player\n",
+ " taking action, and a randomization factor,\n",
+ " returns the most rewarding non-lethal action\n",
+ " or a non-lethal random action and expected reward\n",
+ " '''\n",
+ " viable_actions = game_engine.get_viable_actions(id)\n",
+ " state = query_state(id)\n",
+ "\n",
+ " if viable_actions.size < 1:\n",
+ " best_action = 0\n",
+ " elif np.random.uniform() < epsilon:\n",
+ " best_action = np.random.choice(viable_actions)\n",
+ " else:\n",
+ " qs = [q_net.use(np.hstack(\n",
+ " (state, action)).reshape((1, -1))) for action in viable_actions]\n",
+ " best_action = viable_actions[np.argmin(qs)]\n",
+ "\n",
+ " X = np.hstack((state, best_action))\n",
+ " q = q_net.use(X.reshape((1, -1)))\n",
+ "\n",
+ " return X, q"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "06cd085e-77f4-4a22-9b1f-ec364b7737c5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def update_q(q, old_X, new_X, new_q, outcome, n_epochs, discount=0.9, lr=0.2):\n",
+ " '''\n",
+ " given a q network, the previous state/action pair,\n",
+ " the new state/action pair, the expected next reward,\n",
+ " the outcome of the last action, the number of epochs,\n",
+ " a discount factor (gamma), and the learning rate\n",
+ " updates q with discounted rewards.\n",
+ " '''\n",
+ " reward = reinforcement(outcome)\n",
+ " if outcome == multiplayer.CollisionType.GOAL:\n",
+ " q.train(np.array([new_X]),\n",
+ " np.array([reward]) + np.array([[reward]]),\n",
+ " n_epochs, lr, method='sgd', verbose=False)\n",
+ " else:\n",
+ " q.train(np.array([old_X]),\n",
+ " discount * np.array([new_q]), n_epochs,\n",
+ " lr, method='sgd', verbose=False)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "f51c3238-c918-40a5-bf38-1456f4ed4ff5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gamma = 0.9\n",
+ "n_epochs = 10\n",
+ "learning_rate = 0.015\n",
+ "\n",
+ "hidden_layers = [15]\n",
+ "q = nn.NeuralNetwork(2, hidden_layers, 1)\n",
+ "q.setup_standardization([5, 3.5], [4, np.sqrt(5.25)], [-.1], [0.2])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "072ef9b7-86ec-4cbf-a315-dd6b4019fce6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_steps = 25000\n",
+ "epsilon = 1\n",
+ "final_epsilon = 0.05\n",
+ "epsilon_decay = np.exp(np.log(final_epsilon) / (n_steps))\n",
+ "epsilon_trace = np.zeros(n_steps)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "720a04aa-b53f-42d7-adf8-7c1a0958ff04",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Scoreboard():\n",
+ " ''' tracks game statistics '''\n",
+ " def __init__(self):\n",
+ " self.all_goals = 0\n",
+ " self._deaths = 0\n",
+ " self._goals = 0\n",
+ " self._max_goals = 0\n",
+ "\n",
+ " self.goals = []\n",
+ " self.deaths = []\n",
+ " self.max_goals = []\n",
+ "\n",
+ " def track_outcome(self, outcome):\n",
+ " if outcome == multiplayer.CollisionType.GOAL:\n",
+ " self._goals += 1\n",
+ " self.all_goals += 1\n",
+ " if self._goals > self._max_goals:\n",
+ " self._max_goals = self._goals\n",
+ " elif outcome == multiplayer.CollisionType.DEATH:\n",
+ " self._deaths += 1\n",
+ " self._goals = 0\n",
+ "\n",
+ " def flush(self):\n",
+ " self.goals.append(self._goals)\n",
+ " self.deaths.append(self._deaths)\n",
+ " self.max_goals.append(self._max_goals)\n",
+ "\n",
+ " self._reset()\n",
+ "\n",
+ " def _reset(self):\n",
+ " self._deaths = 0\n",
+ " self._goals = 0\n",
+ " self._max_goals = 0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "c86cea77-c3b9-44fa-becd-2d04d49b92cc",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def plot_status(q, step, epsilon_trace, r_trace):\n",
+ " \n",
+ " plt.subplot(4, 3, 1)\n",
+ " plt.plot(epsilon_trace[:step + 1])\n",
+ " plt.ylabel('Random Action Probability ($\\epsilon$)')\n",
+ " plt.ylim(0, 1)\n",
+ "\n",
+ " plt.subplot(4, 3, 2)\n",
+ " plt.plot(scoreboard.deaths)\n",
+ " plt.ylabel('Deaths')\n",
+ "\n",
+ " plt.subplot(4, 3, 3)\n",
+ " plt.plot(scoreboard.goals)\n",
+ " plt.ylabel('Goals')\n",
+ "\n",
+ " plt.subplot(4, 3, 4)\n",
+ " plt.plot(scoreboard.max_goals)\n",
+ " plt.ylabel('Max Score')\n",
+ "\n",
+ " plt.subplot(4, 3, 5)\n",
+ " plt.plot(r_trace[:step + 1], alpha=0.5)\n",
+ " binSize = 20\n",
+ " if step+1 > binSize:\n",
+ " # Calculate mean of every bin of binSize reinforcement values\n",
+ " smoothed = np.mean(r_trace[:int(step / binSize) * binSize].reshape((int(step / binSize), binSize)), axis=1)\n",
+ " plt.plot(np.arange(1, 1 + int(step / binSize)) * binSize, smoothed)\n",
+ " plt.ylabel('Mean reinforcement')\n",
+ "\n",
+ " plt.subplot(4, 3, 6)\n",
+ " q.draw(['$o$', '$a$'], ['q'])\n",
+ "\n",
+ " plt.tight_layout()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "00ca3585-8a11-4fd5-93d7-8e73bfc31e81",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAH1CAYAAADrrp30AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACtbUlEQVR4nOzdeVxUVf8H8M8Aw6aACMqiiKCYCy4JqbjkkmJqubS5JJapRWYqWCqpqWhiZmplSCpq/ir1yaWnnscFNMUFXFBww0fJDVIQWQTcGJb7+4NmZJgZGGBm7gCfdy9eL+bcc+753hnm5Peee8+VCIIggIiIiIiIiIh0zkTsAIiIiIiIiIjqKibdRERERERERHrCpJuIiIiIiIhIT5h0ExEREREREekJk24iIiIiIiIiPWHSTURERERERKQnTLqJiIiIiIiI9IRJNxEREREREZGeMOkmIiIiIiIi0hMm3URERERERER6UueS7qNHj+LVV1+Fq6srJBIJfvvtt0rbxMTEwMfHB5aWlvD09ERERIT+AyUiIiIiIqI6T9Sku7CwEKmpqbh69Sqys7N1ss9Hjx6hc+fOWLt2rVb1b968iaFDh6JPnz5ISEjAZ599hunTp2PXrl06iYeIiIiIiIjqL4kgCIIhO3z48CF+/vlnbNu2DadPn0ZBQYFiW/PmzeHv74/3338fL7zwQo37kkgk2LNnD0aOHKmxzpw5c/D777/jypUrirLAwECcP38ecXFxNY6BiIiIiIiI6i8zQ3a2evVqfPHFF2jZsiWGDx+OuXPnolmzZrCyskJ2djYuXbqEY8eOYdCgQejRowe+++47eHl56TWmuLg4+Pv7K5UNHjwYkZGRKCwshFQqVWlTUFCgdLKgpKQE2dnZcHBwgEQi0Wu8RFQ5QRCQn58PV1dXmJjUubtoaoWSkhLcvXsXNjY2HBeJjADHRfFxXCQyLoYcFw2adMfGxuLw4cPo2LGj2u3dunXDe++9h4iICERGRiImJkbvSXd6ejqcnJyUypycnFBUVITMzEy4uLiotAkLC8PixYv1GhcR1VxqaiqaN28udhj10t27d+Hm5iZ2GERUDsdF8XBcJDJOhhgXDZp0//rrr1rVs7CwwNSpU/UczTPlzzbKr7jXdBYyJCQEwcHBite5ublo0aIFUlNTYWtrq79AiUgreXl5cHNzg42Njdih1Fvy957jIpFx4LgoPo6LRMbFkOOiQZPusgYOHIhZs2ZhyJAhSuXFxcUwNTU1WBzOzs5IT09XKsvIyICZmRkcHBzUtrGwsICFhYVKua2tLQdRIiPCy/fEI3/vOS4SGReOi+LhuEhknAwxLop2U098fDxatmwJoHQFcbnIyEgEBAQYLA4/Pz9ER0crlUVFRcHX11ft/dxERERERERE2hIt6ZbJZIqp/M6dO+PGjRsAgJ49e+LQoUPV3u/Dhw+RmJiIxMREAKUJfWJiIlJSUgCUXho+YcIERf3AwEDcvn0bwcHBuHLlCjZt2oTIyEh88skn1Y6BiIiIiIiICBDx8vLWrVvj1KlTsLGxwaNHj/DgwQMApfe71OSZ3fHx8ejfv7/itfze63feeQdbtmxBWlqaIgEHAA8PD+zduxdBQUH4/vvv4erqim+//Ravv/56tWMgIiIiIiIiAkRMuqdOnYrJkyfD3d0dnTt3xvr16xEREYFjx46prCZeFf369UNFjx7fsmWLSlnfvn1x7ty5avdJREREREREpI5oSXdgYCCaNGmC5ORkTJkyBWPGjIGnpyfS0tIwbdo0scIiIiIiIiIi0hnRkm4ASpdw79u3D3v27IFMJsOYMWNEjIqIiIiIiIhIN0RNussyMzPDm2++KXYYRERERERERDpj0NXLyy5gpo07d+7oKRIiIiIiIiIi/TNo0v3CCy9gypQpOH36tMY6ubm52LBhA7y9vbF7924DRkdERERERESkWwa9vPzKlStYtmwZXn75ZUilUvj6+sLV1RWWlpbIyclBUlISLl++DF9fX3z11VcYMmSIIcMjIiIiIiIi0imDznQ3btwYK1euxN27d7Fu3Tq0adMGmZmZSE5OBgC8/fbbOHv2LE6cOMGEm4iIiIiIiGo9URZSs7S0xGuvvYbXXntNjO6JiIiIiIiIDMKgM91ERERERERE9QmTbiIiIiIiIiI9YdJNREREREREpCdMuomIiIiIiIj0RLSk+91338XRo0fF6p6IiIiIiIhI70RLuvPz8+Hv7w8vLy8sW7YMd+7cESsUIiIiIiIiIr0QLenetWsX7ty5g2nTpuHXX39Fy5YtMWTIEOzcuROFhYVihUVERERERESkM6Le0+3g4IAZM2YgISEBp0+fRuvWrREQEABXV1cEBQUhOTlZzPCIiIiIiIiIasQoFlJLS0tDVFQUoqKiYGpqiqFDh+Ly5cto3749Vq9eLXZ4RERGLTw8HB4eHrC0tISPjw+OHTtWYf2YmBj4+PjA0tISnp6eiIiI0Fh3+/btkEgkGDlypI6jJiLSH46LRGRMREu6CwsLsWvXLrzyyitwd3fHr7/+iqCgIKSlpeHHH39EVFQU/u///g+hoaFihUhEZPR27NiBmTNnYt68eUhISECfPn0wZMgQpKSkqK1/8+ZNDB06FH369EFCQgI+++wzTJ8+Hbt27VKpe/v2bXzyySfo06ePvg+DiEhnOC4SkbGRCIIgiNGxo6MjSkpKMHbsWEyZMgVdunRRqZOTk4OuXbvi5s2bhg+wCvLy8mBnZ4fc3FzY2tqKHQ5RvVefvpPdu3dH165dsW7dOkVZu3btMHLkSISFhanUnzNnDn7//XdcuXJFURYYGIjz588jLi5OUVZcXIy+ffti4sSJOHbsGB48eIDffvtNYxwFBQUoKChQvM7Ly4Obm1u9+AyIagOOixwXiUiZIcdF0Wa6V69ejbt37+L7779Xm3ADgL29vdEn3EREYpHJZDh79iz8/f2Vyv39/REbG6u2TVxcnEr9wYMHIz4+XmkRy9DQUDRp0gSTJk3SKpawsDDY2dkpftzc3Kp4NERENcdxkYiMkWhJd9++fWFhYaFSLgiCxst/iIjomczMTBQXF8PJyUmp3MnJCenp6WrbpKenq61fVFSEzMxMAMCJEycQGRmJDRs2aB1LSEgIcnNzFT+pqalVPBoioprjuEhExshMrI49PDyQlpaGpk2bKpVnZ2fDw8MDxcXFIkVGRFS7SCQSpdeCIKiUVVZfXp6fn4/x48djw4YNcHR01DoGCwsLtSdSiYjEwHGRiIyJaEm3psHv4cOHsLS0FCEiIqLaxdHREaampiqzNxkZGSqzNnLOzs5q65uZmcHBwQGXL1/GrVu38Oqrryq2l5SUAADMzMxw9epVtGrVSsdHQkSkGxwXicgYGTzpDg4OBlB65nDBggWwtrZWbCsuLsapU6c03uNNRETPmJubw8fHB9HR0Rg1apSiPDo6GiNGjFDbxs/PD3/88YdSWVRUFHx9fSGVStG2bVtcvHhRafv8+fORn5+Pb775hvckEpFR47hIRMbI4El3QkICgNKZ7osXL8Lc3FyxzdzcHJ07d8Ynn3xi6LCIiGql4OBgBAQEwNfXF35+fli/fj1SUlIQGBgIoPSewjt37mDr1q0ASlfkXbt2LYKDgzFlyhTExcUhMjIS27ZtAwBYWlrC29tbqY9GjRoBgEo5EZEx4rhIRMbG4En34cOHAQATJ07EN998w0cmEBHVwOjRo5GVlYXQ0FCkpaXB29sbe/fuhbu7OwAgLS1NaXFKDw8P7N27F0FBQfj+++/h6uqKb7/9Fq+//rpYh0BEpFMcF4nI2Ij2nO66pD49+5KoNuB3Unz8DIiMC7+T4uNnQGRcDPmdNOhMd3BwMJYsWYIGDRoo7u3WZNWqVQaKioiIiIiIiEg/DPqc7oSEBBQWFip+1/STmJhYo37Cw8Ph4eEBS0tL+Pj44NixYxXW//nnn9G5c2dYW1vDxcUFEydORFZWVo1iICIiIiIiIjLoTLf8fu7yv+vSjh07MHPmTISHh6NXr1744YcfMGTIECQlJaFFixYq9Y8fP44JEyZg9erVePXVV3Hnzh0EBgZi8uTJ2LNnj15iJCIiIiIiovrBoDPdhrBq1SpMmjQJkydPRrt27bBmzRq4ublh3bp1auufPHkSLVu2xPTp0+Hh4YHevXvjgw8+QHx8vIEjJyIiIiIiorrG4Pd0a6s693TLZDKcPXsWc+fOVSr39/dHbGys2jY9e/bEvHnzsHfvXgwZMgQZGRnYuXMnhg0bprGfgoICFBQUKF7n5eVVOVYiIiIiIiKq+wyadMuf0a0vmZmZKC4uhpOTk1K5k5MT0tPT1bbp2bMnfv75Z4wePRpPnz5FUVERhg8fju+++05jP2FhYVi8eLFOYyciIiIiIqK6R7R7uvVJIpEovRYEQaVMLikpCdOnT8fnn3+OwYMHIy0tDZ9++ikCAwMRGRmptk1ISIjSrH1eXh7c3Nx0dwBERERERERUJxjlI8MkEgm+/vrrKu/f0dERpqamKrPaGRkZKrPfcmFhYejVqxc+/fRTAECnTp3QoEED9OnTB0uXLoWLi4tKGwsLC1hYWFQ5PiIiIiIiIqpfDH55edlHhmmiaVa6Mubm5vDx8UF0dDRGjRqlKI+OjsaIESPUtnn8+DHMzJTfBlNTUwClM+RERERERERE1VXnHhkWHByMgIAA+Pr6ws/PD+vXr0dKSgoCAwMBlF4afufOHWzduhUA8Oqrr2LKlClYt26d4vLymTNnolu3bnB1ddVLjERERERERFQ/GDTp1kQ+o1zdGe6yRo8ejaysLISGhiItLQ3e3t7Yu3cv3N3dAQBpaWlISUlR1H/33XeRn5+PtWvXYtasWWjUqBEGDBiAL7/8ssaxEBERERERUf0mEUS8hjoyMhKrV69GcnIyAMDLywszZ87E5MmTxQqpWvLy8mBnZ4fc3FzY2tqKHQ5RvcfvpPj4GRAZF34nxcfPgMi4GPI7KdpM94IFC7B69Wp8/PHH8PPzAwDExcUhKCgIt27dwtKlS8UKjYiIiIiIiEgnREu6161bhw0bNmDs2LGKsuHDh6NTp074+OOPmXQTERERERFRrWciVsfFxcXw9fVVKffx8UFRUZEIERERERERERHplmhJ9/jx47Fu3TqV8vXr1+Ptt98WISIiIiIiIiIi3TLo5eXBwcGK3yUSCTZu3IioqCj06NEDAHDy5EmkpqZiwoQJhgyLiIiIiIiISC8MmnQnJCQovfbx8QEAXL9+HQDQpEkTNGnSBJcvXzZkWERERERERER6YdCk+/Dhw4bsjoiIiIiIiEhUot3TTURERERERFTXifbIMLmkpCSkpKRAJpMplQ8fPlykiIiIiIiIiIh0Q7Sk+8aNGxg1ahQuXrwIiUQCQRAAlC6wBpQ+UoyIiIiIiIioNhPt8vIZM2bAw8MD9+7dg7W1NS5fvoyjR4/C19cXR44cESssIiIiIiIiIp0RbaY7Li4Of/75J5o0aQITExOYmJigd+/eCAsLw/Tp01VWOiciIiIiIiKqbUSb6S4uLkbDhg0BAI6Ojrh79y4AwN3dHVevXhUrLCIiIiIiIiKdES3p9vb2xoULFwAA3bt3x4oVK3DixAmEhobC09NTrLCIiGqd8PBweHh4wNLSEj4+Pjh27FiF9WNiYuDj4wNLS0t4enoiIiJCafuGDRvQp08f2Nvbw97eHgMHDsTp06f1eQhERDrFcZGIjIloSff8+fNRUlICAFi6dClu376NPn36YO/evfj222/FCouIqFbZsWMHZs6ciXnz5iEhIQF9+vTBkCFDkJKSorb+zZs3MXToUPTp0wcJCQn47LPPMH36dOzatUtR58iRIxg7diwOHz6MuLg4tGjRAv7+/rhz546hDouIqNo4LhKRsZEI8mXDjUB2djbs7e0VK5jXFnl5ebCzs0Nubi5sbW3FDoeo3qtP38nu3buja9euWLdunaKsXbt2GDlyJMLCwlTqz5kzB7///juuXLmiKAsMDMT58+cRFxento/i4mLY29tj7dq1mDBhgto6BQUFKCgoULzOy8uDm5tbvfgMiGoDjoscF4lImSHHRdFmussSBAGCIKBx48a1LuEmIhKLTCbD2bNn4e/vr1Tu7++P2NhYtW3i4uJU6g8ePBjx8fEoLCxU2+bx48coLCxE48aNNcYSFhYGOzs7xY+bm1sVj4aIqOY4LhKRMRI16Y6MjIS3tzcsLS1haWkJb29vbNy4UcyQiIhqjczMTBQXF8PJyUmp3MnJCenp6WrbpKenq61fVFSEzMxMtW3mzp2LZs2aYeDAgRpjCQkJQW5uruInNTW1ikdDRFRzHBeJyBiJ9siwBQsWYPXq1fj444/h5+cHoPRMY1BQEG7duoWlS5eKFRoRUa1S/gohQRAqvGpIXX115QCwYsUKbNu2DUeOHIGlpaXGfVpYWMDCwqIqYRMR6Q3HRSIyJqIl3evWrcOGDRswduxYRdnw4cPRqVMnfPzxx0y6iYgq4ejoCFNTU5XZm4yMDJVZGzlnZ2e19c3MzODg4KBUvnLlSixbtgwHDx5Ep06ddBs8EZEecFwkImMk6nO6fX19Vcp9fHxQVFQkQkRERLWLubk5fHx8EB0drVQeHR2Nnj17qm3j5+enUj8qKgq+vr6QSqWKsq+++gpLlizB/v371Y7VRETGiOMiERkj0ZLu8ePHK60qKbd+/Xq8/fbbIkRERFT7BAcHY+PGjdi0aROuXLmCoKAgpKSkIDAwEEDpPYVlV9YNDAzE7du3ERwcjCtXrmDTpk2IjIzEJ598oqizYsUKzJ8/H5s2bULLli2Rnp6O9PR0PHz40ODHR0RUVRwXicjYGPTy8uDgYMXvEokEGzduRFRUFHr06AEAOHnyJFJTUzU+eoGIiJSNHj0aWVlZCA0NRVpaGry9vbF37164u7sDANLS0pSeTevh4YG9e/ciKCgI33//PVxdXfHtt9/i9ddfV9QJDw+HTCbDG2+8odTXwoULsWjRIoMcFxFRdXFcJCJjY9DndPfv31+rehKJBH/++aeeo9Gd+vTsS6LagN9J8fEzIDIu/E6Kj58BkXEx5HfSoDPdhw8fNmR3RERERERERKISbfVyAHjw4AEiIyNx5coVSCQStG/fHu+99x7s7OzEDIuIiIiIiIhIJ0RbSC0+Ph6tWrXC6tWrkZ2djczMTKxatQqtWrXCuXPnxAqLiIiIiIiISGdEm+kOCgrC8OHDsWHDBpiZlYZRVFSEyZMnY+bMmTh69KhYoRERERERERHphGhJd3x8vFLCDQBmZmaYPXs2n31IREREREREdYJol5fb2toqPa5BLjU1FTY2NjXad3h4ODw8PGBpaQkfHx8cO3aswvoFBQWYN28e3N3dYWFhgVatWmHTpk01ioGIiIiIiIhItJnu0aNHY9KkSVi5ciV69uwJiUSC48eP49NPP8XYsWOrvd8dO3Zg5syZCA8PR69evfDDDz9gyJAhSEpKQosWLdS2eeutt3Dv3j1ERkaidevWyMjIQFFRUbVjICIiIiIiIgJETLpXrlwJiUSCCRMmKBJcqVSKDz/8EMuXL6/2fletWoVJkyZh8uTJAIA1a9bgwIEDWLduHcLCwlTq79+/HzExMbhx4wYaN24MAGjZsmWFfRQUFKCgoEDxOi8vr9rxEhERERERUd0l2uXl5ubm+Oabb5CTk4PExEQkJCQgOzsbq1evhoWFRbX2KZPJcPbsWfj7+yuV+/v7IzY2Vm2b33//Hb6+vlixYgWaNWuGNm3a4JNPPsGTJ0809hMWFgY7OzvFj5ubW7XiJaK6IzU1FX///bfi9enTpzFz5kysX79exKiIiIiISGyiJN2FhYXo378/rl27Bmtra3Ts2BGdOnWCtbV1jfabmZmJ4uJiODk5KZU7OTkhPT1dbZsbN27g+PHjuHTpEvbs2YM1a9Zg586d+OijjzT2ExISgtzcXMVPampqjeImotpv3LhxOHz4MAAgPT0dgwYNwunTp/HZZ58hNDRU5OiIiIiISCyiJN1SqRSXLl2CRCLRy/7L71cQBI19lZSUQCKR4Oeff0a3bt0wdOhQrFq1Clu2bNE4221hYQFbW1ulHyKq3y5duoRu3boBAP71r3/B29sbsbGx+OWXX7BlyxZxgyMiIiIi0Yh2efmECRMQGRmp0306OjrC1NRUZVY7IyNDZfZbzsXFBc2aNYOdnZ2irF27dhAEQelSUSKiihQWFipujTl48CCGDx8OAGjbti3S0tLEDI2ISHTFxcVITExETk6O2KEQERmcaAupyWQybNy4EdHR0fD19UWDBg2Utq9atarK+zQ3N4ePjw+io6MxatQoRXl0dDRGjBihtk2vXr3w66+/4uHDh2jYsCEA4Nq1azAxMUHz5s2rHAMR1U8dOnRAREQEhg0bhujoaCxZsgQAcPfuXTg4OIgcHRGRYc2cORMdO3bEpEmTUFxcjL59+yI2NhbW1tb4z3/+g379+okdIhGRwYg2033p0iV07doVtra2uHbtGhISEhQ/iYmJ1d5vcHAwNm7ciE2bNuHKlSsICgpCSkoKAgMDAZTejz1hwgRF/XHjxsHBwQETJ05EUlISjh49ik8//RTvvfcerKysanqYRFRPfPnll/jhhx/Qr18/jB07Fp07dwZQulij/LJzIqL6YufOnYpx8I8//sDNmzfxv//9DzNnzsS8efNEjo6IyLBEm+mWLzika6NHj0ZWVhZCQ0ORlpYGb29v7N27F+7u7gCAtLQ0pKSkKOo3bNgQ0dHR+Pjjj+Hr6wsHBwe89dZbWLp0qV7iI6K6qV+/fsjMzEReXh7s7e0V5e+//36NF4kkIqptMjMz4ezsDADYu3cv3nzzTbRp0waTJk3Ct99+K3J0RESGZfCk+/Hjx/j000/x22+/obCwEAMHDsS3334LR0dHnfUxdepUTJ06Ve02dQsatW3bFtHR0Trrn4jqJ1NTU6WEGwBatmwpTjBERCJycnJCUlISXFxcsH//foSHhwMo/XegqampyNERERmWwS8vX7hwIbZs2YJhw4ZhzJgxiI6OxocffmjoMIiIdOrevXsICAiAq6srzMzMYGpqqvRDRFSfTJw4EW+99Ra8vb0hkUgwaNAgAMCpU6fQtm1bkaMjIjIsg8907969G5GRkRgzZgwAYPz48ejVqxeKi4v5D1MiqrXeffddpKSkYMGCBXBxcdHbIxGJiGqDRYsWwdvbG6mpqXjzzTcVT3cwNTXF3LlzRY6OiMiwDJ50p6amok+fPorX3bp1g5mZGe7evQs3NzdDh0NEpBPHjx/HsWPH0KVLF7FDISIyCm+88YZK2TvvvCNCJERE4jJ40l1cXAxzc3PlIMzMUFRUZOhQiIh0xs3NDYIgiB0GEZFoqrJA2vTp0/UYCRGRcTF40i0IAt59913FZUYA8PTpUwQGBio9q3v37t2GDo2IqNrWrFmDuXPn4ocffuDiaURUL61evVqrehKJhEk3EdUrBk+61V1WNH78eEOHQURUY/b29kr3bj969AitWrWCtbU1pFKpUt3s7GxDh0dEZFA3b94UOwQiIqNk8KR78+bNhu6SiEgv1qxZI3YIRERERGTkDJ50ExHVFVwQiIhIs7///hu///47UlJSIJPJlLatWrVKpKiIiAzP4M/pJiKqi0xNTZGRkaFSnpWVpffHIYaHh8PDwwOWlpbw8fHBsWPHKqwfExMDHx8fWFpawtPTExERESp1du3ahfbt28PCwgLt27fHnj179BU+EdVBhw4dwnPPPYfw8HB8/fXXOHz4MDZv3oxNmzYhMTFR7/1zXCQiY8Kkm4hIBzStXF5QUKDyxAZd2rFjB2bOnIl58+YhISEBffr0wZAhQ5CSkqK2/s2bNzF06FD06dMHCQkJ+OyzzzB9+nTs2rVLUScuLg6jR49GQEAAzp8/j4CAALz11ls4deqU3o6DiOqWkJAQzJo1C5cuXYKlpSV27dqF1NRU9O3bF2+++aZe++a4SETGRiLwGTc1lpeXBzs7O+Tm5sLW1lbscIjqPUN+J+WPyAkKCsKSJUvQsGFDxbbi4mIcPXoUt27dQkJCgl767969O7p27Yp169Ypytq1a4eRI0ciLCxMpf6cOXPw+++/48qVK4qywMBAnD9/HnFxcQCA0aNHIy8vD/v27VPUefnll2Fvb49t27ZpFRfHRSLjYujvpI2NDRITE9GqVSvY29vj+PHj6NChA86fP48RI0bg1q1beuub4yIRacOQ30ne001EVAPyR+QIgoCIiAilS8nNzc3RsmVLtZcp6oJMJsPZs2cxd+5cpXJ/f3/ExsaqbRMXFwd/f3+lssGDByMyMhKFhYWQSqWIi4tDUFCQSp2KFo4rKChAQUGB4nVeXl4Vj4aI6pIGDRooxgRXV1dcv34dHTp0AABkZmbqrV+Oi0RkjERNug8dOoRDhw4hIyMDJSUlSts2bdokUlRERNqTPyKnf//+2L17N+zt7Q3Wd2ZmJoqLi+Hk5KRU7uTkhPT0dLVt0tPT1dYvKipCZmYmXFxcNNbRtE8ACAsLw+LFi6t5JERU1/To0QMnTpxA+/btMWzYMMyaNQsXL17E7t270aNHD731y3GRiIyRaPd0L168GP7+/jh06BAyMzORk5Oj9ENEVJscPnzYoAl3WWWfFQ6UzrqXL6usfvnyqu4zJCQEubm5ip/U1FSt4yeiumfVqlXo3r07AGDRokUYNGgQduzYAXd3d0RGRuq9f46LRGRMRJvpjoiIwJYtWxAQECBWCEREOmXox+M4OjrC1NRUZaYlIyNDZUZGztnZWW19MzMzODg4VFhH0z4BwMLCAhYWFtU5DCKqgzw9PRW/W1tbIzw83CD9clwkImMk2ky3TCZDz549xeqeiEinxHg8jrm5OXx8fBAdHa1UHh0drXF89fPzU6kfFRUFX19fSKXSCutwzCaiqjp79ix++ukn/Pzzz3pbULIsjotEZJQEkcyePVsIDQ0Vq3udys3NFQAIubm5YodCRII438kXXnhBWLBggSAIgtCwYUPh+vXrQn5+vjB8+HAhPDxcb/1u375dkEqlQmRkpJCUlCTMnDlTaNCggXDr1i1BEARh7ty5QkBAgKL+jRs3BGtrayEoKEhISkoSIiMjBalUKuzcuVNR58SJE4KpqamwfPly4cqVK8Ly5csFMzMz4eTJk1rHxXGRyLgY+jt57949oX///oJEIhHs7e2FRo0aCRKJRBgwYICQkZGh1745LhKRNgz5nRTt8vKnT59i/fr1OHjwIDp16qQ4kyinj0sxiYj05cqVK4rHxpiZmeHJkydo2LAhQkNDMWLECHz44Yd66Xf06NHIyspCaGgo0tLS4O3tjb1798Ld3R0AkJaWpvRsWg8PD+zduxdBQUH4/vvv4erqim+//Ravv/66ok7Pnj2xfft2zJ8/HwsWLECrVq2wY8cOxf2ZRESV+fjjj5GXl4fLly+jXbt2AICkpCS88847mD59utaP2aoOjotEZGxEe053//79NW6TSCT4888/DRhNzfC5i0TGRYzvpLOzM/7880+0b98eHTp0QFhYGIYPH47z58+jV69eePjwoUHiMBYcF4mMi6G/k3Z2djh48CBeeOEFpfLTp0/D398fDx480HsMxobjIpFxqRfP6T58+LBYXRMR6ZxYj8chIjJGJSUlKlcxAoBUKlV5TCwRUV0n2kJqRER1idiPxyEiMiYDBgzAjBkzcPfuXUXZnTt3EBQUhJdeeknEyIiIDE+0mW4AePDgASIjI3HlyhVIJBK0a9cOkyZNgp2dnZhhERFVmViPxyEiMkZr167FiBEj0LJlS7i5uUEikeD27dvo1KkTfvrpJ7HDIyIyKNGS7vj4eAwePBhWVlbo1q0bBEHA6tWrsWzZMkRFRaFr165ihUZEVC0PHjzAzp07cf36dXz66ado3Lgxzp07BycnJzRr1kzs8IiIDMbNzQ3nzp3DwYMHceXKFQiCgPbt22PgwIFih0ZEZHCiJd1BQUEYPnw4NmzYADOz0jCKioowefJkzJw5E0ePHhUrNCKiKrtw4QIGDhwIOzs73Lp1C1OmTEHjxo2xZ88e3L59G1u3bhU7RCIivXvy5AkOHTqEV155BQBw6NAhFBQUAABu3bqFqKgohIaGwtLSUswwiYgMSrR7uuPj4zFnzhxFwg2UPmZn9uzZiI+PFyssIqJqCQ4Oxrvvvovk5GSlf0wOGTKEJxGJqN7YunUrfvjhB8XrtWvXIjY2FgkJCUhISMD//d//Yd26dSJGSERkeKIl3ba2tkrPSJRLTU2FjY2NCBEREVXfmTNn8MEHH6iUN2vWDOnp6SJERERkeD///DPee+89pbJffvkFhw8fxuHDh/HVV1/hX//6l0jRERGJQ7Ske/To0Zg0aRJ27NiB1NRU/P3339i+fTsmT56MsWPHihUWEVG1WFpaIi8vT6X86tWraNKkiQgREREZ3rVr19CmTRvFa0tLS5iYPPvnZrdu3ZCUlCRGaEREohHtnu6VK1dCIpFgwoQJKCoqAlD67MYPP/wQy5cvFyssIqJqGTFiBEJDQxUzOBKJBCkpKZg7dy5ef/11kaMjIjKM3NxcpVsH79+/r7S9pKREcY83EVF9IdpMt7m5Ob755hvk5OQgMTERCQkJyM7OxurVq2FhYVGjfYeHh8PDwwOWlpbw8fHBsWPHtGp34sQJmJmZoUuXLjXqn4jqn5UrV+L+/fto2rQpnjx5gr59+6J169awsbHBF198IXZ4REQG0bx5c1y6dEnj9gsXLqB58+YGjIiISHyiPqcbKH2ebceOHXW2vx07dmDmzJkIDw9Hr1698MMPP2DIkCFISkpCixYtNLbLzc3FhAkT8NJLL+HevXs6i4eI6gdbW1scP34chw8fxtmzZ1FSUoKuXbvy8ThEVK8MHToUn3/+OYYNG6ayQvmTJ0+wePFiDBs2TKToiIjEIREEQTBUZ8HBwViyZAkaNGiA4ODgCuuuWrWqWn10794dXbt2VVoZs127dhg5ciTCwsI0thszZgy8vLxgamqK3377DYmJiVr3mZeXBzs7O+Tm5sLW1rZacROR7hj6O1lSUoItW7Zg9+7duHXrFiQSCTw8PPDGG28gICAAEolE7zEYG46LRMbFUN/Je/fuoUuXLjA3N8e0adPQpk0bSCQS/O9//8PatWtRVFSEhIQEODk56S0GY8Vxkci4GPI7adCZ7oSEBBQWFip+16S6/0CVyWQ4e/Ys5s6dq1Tu7++P2NhYje02b96M69ev46effsLSpUsr7aegoEDpfiR1iycRUf0gCAKGDx+OvXv3onPnzujYsSMEQcCVK1fw7rvvYvfu3fjtt9/EDpOIyCCcnJwQGxuLDz/8EHPnzoV8bkcikWDQoEEIDw+vlwk3EdVvBk26Dx8+rPj9xx9/RPPmzZVWtARK/wGbmpparf1nZmaiuLhYZTB3cnLS+Mie5ORkzJ07F8eOHVNa+KMiYWFhWLx4cbViJKK6ZcuWLTh69CgOHTqE/v37K237888/MXLkSGzduhUTJkwQKUIiIsPy8PDA/v37kZ2djb/++gsA0Lp1azRu3FjkyIiIxCHaQmoeHh7IzMxUKc/OzoaHh0eN9l1+plwQBLWz58XFxRg3bhwWL16s9HiLyoSEhCA3N1fxU92TBERU+23btg2fffaZSsINAAMGDMDcuXPx888/ixAZEZG4GjdujG7duqFbt25MuImoXhMt6dZ0K/nDhw9VFt7QlqOjI0xNTVVmtTMyMtReypSfn4/4+HhMmzYNZmZmMDMzQ2hoKM6fPw8zMzP8+eefavuxsLCAra2t0g8R1U8XLlzAyy+/rHH7kCFDcP78eQNGRERERETGxOCrl8sXUJNIJPj8889hbW2t2FZcXIxTp05V+5Fd5ubm8PHxQXR0NEaNGqUoj46OxogRI1Tq29ra4uLFi0pl4eHh+PPPP7Fz584az7gTUd2XnZ1d4f2JTk5OyMnJMWBERERERGRMDJ50yxdQEwQBFy9ehLm5uWKbubk5OnfujE8++aTa+w8ODkZAQAB8fX3h5+eH9evXIyUlBYGBgQBKLw2/c+cOtm7dChMTE3h7eyu1b9q0KSwtLVXKiYjUKS4urnA9CFNTUxQVFRkwIiIiIiIyJgZPuuWLqU2cOBHffPONzi/NHj16NLKyshAaGoq0tDR4e3tj7969cHd3BwCkpaUhJSVFp30SUf0lCALeffddWFhYqN1e9kkHRERERFT/GPQ53XUVn7tIZFwM+Z2cOHGiVvU2b96s1ziMDcdFIuPC76T4+BkQGZc6+5zussLCwuDk5IT33ntPqXzTpk24f/8+5syZI1JkRETaq2/JNBERERFVjWirl//www9o27atSnmHDh0QEREhQkREREREREREuiVa0p2eng4XFxeV8iZNmiAtLU2EiIiIiIiIiIh0S7Sk283NDSdOnFApP3HiBFxdXUWIiIiIiIiIiEi3RLune/LkyZg5cyYKCwsxYMAAAMChQ4cwe/ZszJo1S6ywiIiIiIiIiHRGtJnu2bNnY9KkSZg6dSo8PT3h6emJjz/+GNOnT0dISIhYYRER1Ro5OTkICAiAnZ0d7OzsEBAQgAcPHlTYRhAELFq0CK6urrCyskK/fv1w+fJlxfbs7Gx8/PHHeO6552BtbY0WLVpg+vTpyM3N1fPREBHVHMdFIjJGoiXdEokEX375Je7fv4+TJ0/i/PnzyM7Oxueff47ExESxwiIiqjXGjRuHxMRE7N+/H/v370diYiICAgIqbLNixQqsWrUKa9euxZkzZ+Ds7IxBgwYhPz8fAHD37l3cvXsXK1euxMWLF7Flyxbs378fkyZNMsQhERHVCMdFIjJKgpF48OCB8P333wvPP/+8YGJiInY4VZKbmysAEHJzc8UOhYiE+vGdTEpKEgAIJ0+eVJTFxcUJAIT//e9/atuUlJQIzs7OwvLlyxVlT58+Fezs7ISIiAiNff3rX/8SzM3NhcLCQq3jqw+fAVFtUh++kxwXiagqDPmdFG2mW+7PP//E+PHj4eLigu+++w5Dhw5FfHy82GERERm1uLg42NnZoXv37oqyHj16wM7ODrGxsWrb3Lx5E+np6fD391eUWVhYoG/fvhrbAEBubi5sbW1hZqZ5GZCCggLk5eUp/RARGRLHRSIyVqIk3X///TeWLl0KT09PjB07Fvb29igsLMSuXbuwdOlSPP/882KERURUa6Snp6Np06Yq5U2bNkV6errGNgDg5OSkVO7k5KSxTVZWFpYsWYIPPvigwnjCwsIU91Da2dnBzc1Nm8MgItIZjotEZKwMnnQPHToU7du3R1JSEr777jvcvXsX3333naHDICIySosWLYJEIqnwR341kEQiUWkvCILa8rLKb9fUJi8vD8OGDUP79u2xcOHCCvcZEhKC3NxcxU9qamplh0pEpBWOi0RU2xn8kWFRUVGYPn06PvzwQ3h5eRm6eyIiozZt2jSMGTOmwjotW7bEhQsXcO/ePZVt9+/fV5mxkXN2dgZQOrPj4uKiKM/IyFBpk5+fj5dffhkNGzbEnj17IJVKK4zJwsICFhYWFdYhIqoOjotEVNsZPOk+duwYNm3aBF9fX7Rt2xYBAQEYPXq0ocMgIjJKjo6OcHR0rLSen58fcnNzcfr0aXTr1g0AcOrUKeTm5qJnz55q23h4eMDZ2RnR0dGK23hkMhliYmLw5ZdfKurl5eVh8ODBsLCwwO+//w5LS0sdHBkRUfVwXCSi2s7gl5f7+flhw4YNSEtLwwcffIDt27ejWbNmKCkpQXR0tOLxDEREpFm7du3w8ssvY8qUKTh58iROnjyJKVOm4JVXXsFzzz2nqNe2bVvs2bMHQOnlkzNnzsSyZcuwZ88eXLp0Ce+++y6sra0xbtw4AKUzOf7+/nj06BEiIyORl5eH9PR0pKeno7i4WJRjJSLSBsdFIjJWBp/plrO2tsZ7772H9957D1evXkVkZCSWL1+OuXPnYtCgQfj999/FCo2IqFb4+eefMX36dMWqu8OHD8fatWuV6ly9ehW5ubmK17Nnz8aTJ08wdepU5OTkoHv37oiKioKNjQ0A4OzZszh16hQAoHXr1kr7unnzJlq2bKnHIyIiqhmOi0RkjCSCIAhiByFXXFyMP/74A5s2bapVSXdeXh7s7OwUj48gInHxOyk+fgZExoXfSfHxMyAyLob8Tor+nO6yTE1NMXLkyFqVcBMRERERERFpYlRJNxEREREREVFdwqSbiIiIiIiISE+YdBMRERERERHpCZNuIiIiIiIiIj0R7ZFhAPD06VNcuHABGRkZKCkpUdo2fPhwkaIiIiIiIiIi0g3Rku79+/djwoQJyMzMVNkmkUhQXFwsQlREREREREREuiPa5eXTpk3Dm2++ibS0NJSUlCj9MOEmIiIiIiKiukC0pDsjIwPBwcFwcnISKwQiIiIiIiIivRIt6X7jjTdw5MgRsbonIiIiIiIi0jvR7uleu3Yt3nzzTRw7dgwdO3aEVCpV2j59+nSRIiMiIiIiIiLSDdGS7l9++QUHDhyAlZUVjhw5AolEotgmkUiYdBMREREREVGtJ9rl5fPnz0doaChyc3Nx69Yt3Lx5U/Fz48aNGu07PDwcHh4esLS0hI+PD44dO6ax7u7duzFo0CA0adIEtra28PPzw4EDB2rUPxEREREREREgYtItk8kwevRomJjoNoQdO3Zg5syZmDdvHhISEtCnTx8MGTIEKSkpausfPXoUgwYNwt69e3H27Fn0798fr776KhISEnQaFxEREREREdU/EkEQBDE6DgoKQpMmTfDZZ5/pdL/du3dH165dsW7dOkVZu3btMHLkSISFhWm1jw4dOmD06NH4/PPPtaqfl5cHOzs75ObmwtbWtlpxE5Hu8DspPn4GRMaF30nx8TMgMi6G/E6Kdk93cXExVqxYgQMHDqBTp04qC6mtWrWqyvuUyWQ4e/Ys5s6dq1Tu7++P2NhYrfZRUlKC/Px8NG7cWGOdgoICFBQUKF7n5eVVOVYiIiIiIiKq+0RLui9evIjnn38eAHDp0iWlbWUXVauKzMxMFBcXqzz728nJCenp6Vrt4+uvv8ajR4/w1ltvaawTFhaGxYsXVytGIiIiIiIiqj9ES7oPHz6st32XT9oFQdAqkd+2bRsWLVqEf//732jatKnGeiEhIQgODla8zsvLg5ubW/UDJiIiIiIiojpJtKRbHxwdHWFqaqoyq52RkaEy+13ejh07MGnSJPz6668YOHBghXUtLCxgYWFR43iJiIiIiIiobhM16X7w4AEiIyNx5coVSCQStGvXDpMmTYKdnV219mdubg4fHx9ER0dj1KhRivLo6GiMGDFCY7tt27bhvffew7Zt2zBs2LBq9U1ERERERERUnmiPDIuPj0erVq2wevVqZGdnIzMzE6tXr0arVq1w7ty5au83ODgYGzduxKZNm3DlyhUEBQUhJSUFgYGBAEovDZ8wYYKi/rZt2zBhwgR8/fXX6NGjB9LT05Geno7c3NwaHyMRERERERHVb6LNdAcFBWH48OHYsGEDzMxKwygqKsLkyZMxc+ZMHD16tFr7HT16NLKyshAaGoq0tDR4e3tj7969cHd3BwCkpaUpPbP7hx9+QFFRET766CN89NFHivJ33nkHW7Zsqf4BEhERERERUb0n2nO6rayskJCQgLZt2yqVJyUlwdfXF48fPxYjrGrhcxeJjAu/k+LjZ0BkXPidFB8/AyLjYsjvpGiXl9va2irNOMulpqbCxsZGhIiIiIiIiIiIdEu0pHv06NGYNGkSduzYgdTUVPz999/Yvn07Jk+ejLFjx4oVFhEREREREZHOiJZ0r1y5Eq+99homTJiAli1bwt3dHe+++y7eeOMNfPnll2KFRURUa+Tk5CAgIAB2dnaws7NDQEAAHjx4UGEbQRCwaNEiuLq6wsrKCv369cPly5c11h0yZAgkEgl+++033R8AEZGOcVwkImMkWtJtbm6Ob775Bjk5OUhMTERCQgKys7OxevVqPgObiEgL48aNQ2JiIvbv34/9+/cjMTERAQEBFbZZsWIFVq1ahbVr1+LMmTNwdnbGoEGDkJ+fr1J3zZo1kEgk+gqfiEjnOC4SkTES9TndAGBtbY2OHTuKHQYRUa1y5coV7N+/HydPnkT37t0BABs2bICfnx+uXr2K5557TqWNIAhYs2YN5s2bh9deew0A8OOPP8LJyQm//PILPvjgA0Xd8+fPY9WqVThz5gxcXFwMc1BERDXAcZGIjJVBk+7g4GCt665atUqPkRAR1W5xcXGws7NT/MMSAHr06AE7OzvExsaq/cflzZs3kZ6eDn9/f0WZhYUF+vbti9jYWMU/Lh8/foyxY8di7dq1cHZ21iqegoICFBQUKF7n5eVV99CIiKqF4yIRGSuDJt0JCQlKr8+ePYvi4mLFIHjt2jWYmprCx8fHkGEREdU66enpaNq0qUp506ZNkZ6errENADg5OSmVOzk54fbt24rXQUFB6NmzJ0aMGKF1PGFhYVi8eLHW9YmIdI3jIhEZK4Pe03348GHFz6uvvop+/frh77//xrlz53Du3Dmkpqaif//+GDZsmCHDIiIyGosWLYJEIqnwJz4+HgDU3lcoCEKl9xuW3162ze+//44///wTa9asqVLcISEhyM3NVfykpqZWqT0RkSYcF4mothPtnu6vv/4aUVFRsLe3V5TZ29tj6dKl8Pf3x6xZs8QKjYhINNOmTcOYMWMqrNOyZUtcuHAB9+7dU9l2//59lRkbOfklkenp6Ur3I2ZkZCja/Pnnn7h+/ToaNWqk1Pb1119Hnz59cOTIEbX7trCw4CKYRKQXHBeJqLYTLenOy8vDvXv30KFDB6XyjIwMtatFEhHVB46OjnB0dKy0np+fH3Jzc3H69Gl069YNAHDq1Cnk5uaiZ8+eatt4eHjA2dkZ0dHReP755wEAMpkMMTExikc1zp07F5MnT1Zq17FjR6xevRqvvvpqTQ6NiKhaOC4SUW0nWtI9atQoTJw4EV9//TV69OgBADh58iQ+/fRTxeqRRESkXrt27fDyyy9jypQp+OGHHwAA77//Pl555RWlxYLatm2LsLAwjBo1ChKJBDNnzsSyZcvg5eUFLy8vLFu2DNbW1hg3bhyA0lkfdYsEtWjRAh4eHoY5OCKiauC4SETGSrSkOyIiAp988gnGjx+PwsJCCIIAqVSKSZMm4auvvhIrLCKiWuPnn3/G9OnTFavuDh8+HGvXrlWqc/XqVeTm5ipez549G0+ePMHUqVORk5OD7t27IyoqCjY2NgaNnYhIHzguEpExkgiCIIgZwKNHj3D9+nUIgoDWrVujQYMGYoZTLXl5ebCzs0Nubi5sbW3FDoeo3uN3Unz8DIiMC7+T4uNnQGRcDPmdFG2mGwAOHTqEQ4cOISMjAyUlJUrbNm3aJFJURERERERERLohWtK9ePFihIaGwtfXFy4uLpU+yoGIiIiIiIiothH1nu4tW7YgICBArBCIiIiIiIiI9MpErI5lMpnGxzcQERERERER1QWiJd2TJ0/GL7/8Ilb3RERERERERHon2uXlT58+xfr163Hw4EF06tQJUqlUafuqVatEioyIiIiIiIhIN0RLui9cuIAuXboAAC5duqS0jYuqERERERERUV0gWtJ9+PBhsbomIiIiIiIiMgjR7ukmIiIiIiIiqutEm+mWS0pKQkpKCmQymVL58OHDRYqIiIiIiIiISDdES7pv3LiBUaNG4eLFi5BIJBAEAcCz+7mLi4vFCo2IiIiIiIhIJ0S7vHzGjBnw8PDAvXv3YG1tjcuXL+Po0aPw9fXFkSNHxAqLiIiIiIiISGdEm+mOi4vDn3/+iSZNmsDExAQmJibo3bs3wsLCMH36dCQkJIgVGhEREREREZFOiDbTXVxcjIYNGwIAHB0dcffuXQCAu7s7rl69KlZYRERERERERDoj2ky3t7c3Lly4AE9PT3Tv3h0rVqyAubk51q9fD09PT7HCIiIiIiIiItIZ0ZLu+fPn49GjRwCApUuX4pVXXkGfPn3g4OCAHTt2iBUWERERERERkc6Idnn54MGD8dprrwEAPD09kZSUhMzMTGRkZOC5556r0b7Dw8Ph4eEBS0tL+Pj44NixYxXWj4mJgY+PDywtLeHp6YmIiIga9U9EREREREQEiJh0qyOTyTBjxgy0bt262vvYsWMHZs6ciXnz5iEhIQF9+vTBkCFDkJKSorb+zZs3MXToUPTp0wcJCQn47LPPMH36dOzatavaMRAREREREREBIlxe/uDBA3z00UeIioqCVCrF3LlzMW3aNCxatAgrV65Ehw4dsGnTpmrvf9WqVZg0aRImT54MAFizZg0OHDiAdevWISwsTKV+REQEWrRogTVr1gAA2rVrh/j4eKxcuRKvv/662j4KCgpQUFCgeJ2bmwsAyMvLq3bcRKQ78u+iIAgiR1J/yd97jotExoHjovg4LhIZF0OOiwZPuj/77DMcPXoU77zzDvbv34+goCDs378fT58+xb59+9C3b99q71smk+Hs2bOYO3euUrm/vz9iY2PVtomLi4O/v79S2eDBgxEZGYnCwkJIpVKVNmFhYVi8eLFKuZubW7VjJyLdy8/Ph52dndhh1Ev5+fkAOC4SGRuOi+LhuEhknAwxLho86f7vf/+LzZs3Y+DAgZg6dSpat26NNm3aKGaaayIzMxPFxcVwcnJSKndyckJ6erraNunp6WrrFxUVITMzEy4uLiptQkJCEBwcrHhdUlKC7OxsODg4QCKRaIwvLy8Pbm5uSE1Nha2tbVUOzSjVteMB6t4x1dfjEQQB+fn5cHV1NWB0VJarqytSU1NhY2PDcbEWq2vHA9S9Y+K4WHtwXOTxGKO6djyAcY6LBk+67969i/bt2wMoXUDN0tJScSm4rpQfyARBqHBwU1dfXbmchYUFLCwslMoaNWqkdXy2trZ15o8aqHvHA9S9Y6qPx8OZHHGZmJigefPmWtevj3+jtUldOx6g7h0Tx0Xjx3GRx2PM6trxAMY1Lhp8IbWSkhKlS7ZNTU3RoEEDnezb0dERpqamKrPaGRkZKrPZcs7Ozmrrm5mZwcHBQSdxERERERERUf1k8JluQRDw7rvvKmaKnz59isDAQJXEe/fu3VXet7m5OXx8fBAdHY1Ro0YpyqOjozFixAi1bfz8/PDHH38olUVFRcHX11ft/dxERERERERE2jJ40v3OO+8ovR4/frxO9x8cHIyAgAD4+vrCz88P69evR0pKCgIDAwGU3o99584dbN26FQAQGBiItWvXIjg4GFOmTEFcXBwiIyOxbds2ncYFlF6WvnDhQpVL02urunY8QN07Jh4PGbu69pnyeIxfXTumunY8VPc+Ux6PcatrxwMY5zFJhDr47Ijw8HCsWLECaWlp8Pb2xurVq/Hiiy8CAN59913cunULR44cUdSPiYlBUFAQLl++DFdXV8yZM0eRpBMRERERERFVV51MuomIiIiIiIiMgcEXUiMiIiIiIiKqL5h0ExEREREREekJk24iIiIiIiIiPWHSTURERERERKQnTLprICcnBwEBAbCzs4OdnR0CAgLw4MGDCtsIgoBFixbB1dUVVlZW6NevHy5fvqxUp1+/fpBIJEo/Y8aMqXHfYhxPdnY2Pv74Yzz33HOwtrZGixYtMH36dOTm5irtp2XLlirHPHfu3CofQ3h4ODw8PGBpaQkfHx8cO3aswvoxMTHw8fGBpaUlPD09ERERoVJn165daN++PSwsLNC+fXvs2bOnxv2KdTwbNmxAnz59YG9vD3t7ewwcOBCnT59WqrNo0SKVz8LZ2dkoj2fLli0qsUokEjx9+rRG/VL1cVw0rnGxro2J+jgmjoscF/WN4yLHxer0K9bxiD0m6uOYjGJcFKjaXn75ZcHb21uIjY0VYmNjBW9vb+GVV16psM3y5csFGxsbYdeuXcLFixeF0aNHCy4uLkJeXp6iTt++fYUpU6YIaWlpip8HDx7UuG8xjufixYvCa6+9Jvz+++/CX3/9JRw6dEjw8vISXn/9daX9uLu7C6GhoUrHnJ+fX6X4t2/fLkilUmHDhg1CUlKSMGPGDKFBgwbC7du31da/ceOGYG1tLcyYMUNISkoSNmzYIEilUmHnzp2KOrGxsYKpqamwbNky4cqVK8KyZcsEMzMz4eTJk9XuV8zjGTdunPD9998LCQkJwpUrV4SJEycKdnZ2wt9//62os3DhQqFDhw5Kn0VGRkaNjkVfx7N582bB1tZWKda0tLQa9Us1w3HReMbFujYm6uuYOC5yXNQ3joscF/lvRXGPyRjGRSbd1ZSUlCQAUPpCxcXFCQCE//3vf2rblJSUCM7OzsLy5csVZU+fPhXs7OyEiIgIRVnfvn2FGTNm6LRvMY+nvH/961+Cubm5UFhYqChzd3cXVq9eXa3Y5bp16yYEBgYqlbVt21aYO3eu2vqzZ88W2rZtq1T2wQcfCD169FC8fuutt4SXX35Zqc7gwYOFMWPGVLtfbenjeMorKioSbGxshB9//FFRtnDhQqFz587VD1wDfRzP5s2bBTs7O532S9XHcdG4xsW6NiZWZ98cF3XTL1Ufx0WOi9XpV1t1bUwUhLo7LvLy8mqKi4uDnZ0dunfvrijr0aMH7OzsEBsbq7bNzZs3kZ6eDn9/f0WZhYUF+vbtq9Lm559/hqOjIzp06IBPPvkE+fn5Nepb7OMpKzc3F7a2tjAzM1Mq//LLL+Hg4IAuXbrgiy++gEwm0zp+mUyGs2fPKsUCAP7+/hpjiYuLU6k/ePBgxMfHo7CwsMI68n1Wp18xj6e8x48fo7CwEI0bN1YqT05OhqurKzw8PDBmzBjcuHGj2scC6Pd4Hj58CHd3dzRv3hyvvPIKEhISatQvVR/HReMZF+vamKjPYyqP4yLpEsdFjov8t6L26vK4aFZ5FVInPT0dTZs2VSlv2rQp0tPTNbYBACcnJ6VyJycn3L59W/H67bffhoeHB5ydnXHp0iWEhITg/PnziI6OrnbfYh5PWVlZWViyZAk++OADpfIZM2aga9eusLe3x+nTpxESEoKbN29i48aNWsWfmZmJ4uJitbFUFL+6+kVFRcjMzISLi4vGOvJ9VqdfMY+nvLlz56JZs2YYOHCgoqx79+7YunUr2rRpg3v37mHp0qXo2bMnLl++DAcHB6M6nrZt22LLli3o2LEj8vLy8M0336BXr144f/48vLy89Pb5kHocF41nXKxrY6I+j6k8joukSxwXOS7y34riH5MxjItMustZtGgRFi9eXGGdM2fOAAAkEonKNkEQ1JaXVX57+TZTpkxR/O7t7Q0vLy/4+vri3Llz6Nq1a5X6NobjkcvLy8OwYcPQvn17LFy4UGlbUFCQ4vdOnTrB3t4eb7zxhuJspra0jaWi+uXLtdlnVfvVlj6OR27FihXYtm0bjhw5AktLS0X5kCFDFL937NgRfn5+aNWqFX788UcEBwdX6zgqiq8mx9OjRw/06NFDsb1Xr17o2rUrvvvuO3z77bfV7peUGcM4wnGxeuNiXRsTq7NvjoscF/XBGMYRjoscF6uzX2MfEzXFWNvHRSbd5UybNk1l5cfyWrZsiQsXLuDevXsq2+7fv69ylkROvqpfenq60pmkjIwMjW0AoGvXrpBKpUhOTkbXrl3h7Oysdd/Gcjz5+fl4+eWX0bBhQ+zZswdSqbTCmORfjL/++kurQdTR0RGmpqYqZ6Mqem+dnZ3V1jczM1P0qamOfJ/V6Vcb+joeuZUrV2LZsmU4ePAgOnXqVGEsDRo0QMeOHZGcnFyNIyml7+ORMzExwQsvvKCIVV+fT31jLONIWRwXKx4X69qYqM9jkuO4yHGxKoxlHCmL42L9Ghfr2pgI1O1xkfd0l+Po6Ii2bdtW+GNpaQk/Pz/k5uYqLaF/6tQp5ObmomfPnmr3Lb8ESH7ZD1B6D0FMTIzGNgBw+fJlFBYWKgaqqvRtDMeTl5cHf39/mJub4/fff1c6U6aJ/D4LdZe5qGNubg4fHx+lWAAgOjpaY/x+fn4q9aOiouDr66sY5DXVke+zOv2KeTwA8NVXX2HJkiXYv38/fH19K42loKAAV65c0fqzUEefx1OWIAhITExUxKqvz6e+MYZxpDyOixWra2OiPo8J4LhYWb+kyhjGkfI4Llasro2LdW1MBOr4uKiT5djqqZdfflno1KmTEBcXJ8TFxQkdO3ZUeWTCc889J+zevVvxevny5YKdnZ2we/du4eLFi8LYsWOVHpnw119/CYsXLxbOnDkj3Lx5U/jvf/8rtG3bVnj++eeFoqKiKvVtDMeTl5cndO/eXejYsaPw119/KS3TLz+e2NhYYdWqVUJCQoJw48YNYceOHYKrq6swfPjwKsUvX+o/MjJSSEpKEmbOnCk0aNBAuHXrliAIgjB37lwhICBAUV/+iIGgoCAhKSlJiIyMVHnEwIkTJwRTU1Nh+fLlwpUrV4Tly5drfAyEpn6rSx/H8+WXXwrm5ubCzp07NT5uY9asWcKRI0eEGzduCCdPnhReeeUVwcbGxiiPZ9GiRcL+/fuF69evCwkJCcLEiRMFMzMz4dSpU1r3S7rFcdF4xsW6Nibq65g4LnJc1DeOixwX+W9FcY/JGMZFJt01kJWVJbz99tuCjY2NYGNjI7z99ttCTk6OUh0AwubNmxWvS0pKhIULFwrOzs6ChYWF8OKLLwoXL15UbE9JSRFefPFFoXHjxoK5ubnQqlUrYfr06UJWVlaV+zaG4zl8+LAAQO3PzZs3BUEQhLNnzwrdu3cX7OzsBEtLS+G5554TFi5cKDx69KjKx/D9998L7u7ugrm5udC1a1chJiZGse2dd94R+vbtq1T/yJEjwvPPPy+Ym5sLLVu2FNatW6eyz19//VV47rnnBKlUKrRt21bYtWtXlfqtCV0fj7u7u9rPYuHChYo68mdnSqVSwdXVVXjttdeEy5cvG+XxzJw5U2jRooVgbm4uNGnSRPD39xdiY2Or1C/pFsdF4xoX69qYqI9j4rjIcVHfOC5yXKys35qoa2OiPo7JGMZFiSD8c6c5EREREREREekU7+kmIiIiIiIi0hMm3URERERERER6wqSbiIiIiIiISE+YdBMRERERERHpCZNuIiIiIiIiIj1h0k1ERERERESkJ0y6iYiIiIiIiPSESTcRERERERGRnjDpJiIiIiIiItITJt1EREREREREesKkm4iIiIiIiEhPmHQTERERERER6QmTbiIiIiIiIiI9YdJNREREREREpCdMuomIiIiIiIj0hEk3ERERERERkZ4w6SYiIiIiIiLSEybdREREREQkuuPHj2Po0KGwt7eHlZUVvLy8sGTJEpU6kydPho+PDywsLCCRSHDr1i1xAlZDIpFg0aJF1WrbsmVLvPLKK5XWS0pKwqJFi4zquKliTLqJiIiIiEhUv/zyC/r27Qs7Ozts3boVe/fuxZw5cyAIglK9Q4cO4eDBg2jRogV69uwpUrSaxcXFYfLkyXrtIykpCYsXL2bSXYuYiR0AERERERHVX3fu3MH777+PDz74AOHh4Yry/v37q9RdsGABFi5cCABYuXIljhw5YqgwtdKjRw+xQyAjxJluIiIiIiISzcaNG/Ho0SPMmTOn0romJtVPX95880106NBBqezVV1+FRCLBr7/+qig7d+4cJBIJ/vjjD0VZeno6PvjgAzRv3hzm5ubw8PDA4sWLUVRUpLQ/dZeXHz9+HH5+frC0tESzZs2wYMECbNy4UeOl8fv370fXrl1hZWWFtm3bYtOmTYptW7ZswZtvvgmg9KSERCKBRCLBli1bAAAJCQl45ZVX0LRpU1hYWMDV1RXDhg3D33//XZ23jHSESTcREREREYnm6NGjaNy4Mf73v/+hS5cuMDMzQ9OmTREYGIi8vDyd9TNw4EAkJSUhLS0NAFBUVISYmBhYWVkhOjpaUe/gwYMwMzNDv379AJQm3N26dcOBAwfw+eefY9++fZg0aRLCwsIwZcqUCvu8cOECBg0ahMePH+PHH39EREQEzp07hy+++EJt/fPnz2PWrFkICgrCv//9b3Tq1AmTJk3C0aNHAQDDhg3DsmXLAADff/894uLiEBcXh2HDhuHRo0cYNGgQ7t27h++//x7R0dFYs2YNWrRogfz8/Jq+fVQDvLyciIiIiIhEc+fOHTx+/BhvvvkmQkJCsGbNGpw5cwYLFy7EpUuXcOzYMUgkkhr3M3DgQAClSXVAQABOnTqF/Px8zJ49W2mm++DBg+jWrRtsbGwAAIsWLUJOTg4uX76MFi1aAABeeuklWFlZ4ZNPPsGnn36K9u3bq+1z6dKlMDU1xaFDh+Do6AigNHHu2LGj2vqZmZk4ceKEop8XX3wRhw4dwi+//IIXX3wRTZo0gZeXFwCgffv2Speznz17FllZWYiMjMSIESMU5W+99Va13i/SHc50ExERERGRaEpKSvD06VN89tlnCAkJQb9+/fDpp58iLCwMJ06cwKFDh3TST6tWrdCyZUscPHgQABAdHY2OHTti/PjxuHnzJq5fv46CggIcP35ckaADwH/+8x/0798frq6uKCoqUvwMGTIEABATE6Oxz5iYGAwYMECRcAOll8hrSoS7dOmiSLgBwNLSEm3atMHt27crPb7WrVvD3t4ec+bMQUREBJKSkiptQ4bBpJuIiIiIiETj4OAAABg8eLBSuTypPXfunM76eumllxRJ/MGDBzFo0CB07NgRTk5OOHjwIE6cOIEnT54oJd337t3DH3/8AalUqvQjvz88MzNTY39ZWVlwcnJSKVdXBjx7L8qysLDAkydPKj02Ozs7xMTEoEuXLvjss8/QoUMHuLq6YuHChSgsLKy0PekPLy8nIiIiIiLRdOrUCSdPnlQplz8urCaLp5X30ksvITIyEqdPn8apU6cwf/58AMCAAQMQHR2N27dvo2HDhkqXbTs6OqJTp04a78N2dXXV2J+DgwPu3bunUp6enl7DI1GvY8eO2L59OwRBwIULF7BlyxaEhobCysoKc+fO1UufVDnOdBMRERERkWhef/11AMC+ffuUyvfu3QtAt4/heumllyCRSLBgwQKYmJjgxRdfBFB6v/fhw4cRHR2NF198EVKpVNHmlVdewaVLl9CqVSv4+vqq/FSUdPft2xd//vmn0mx4SUmJ0j3kVWVhYQEAFc5+SyQSdO7cGatXr0ajRo10erUAVR1nuomIiIiISDT+/v549dVXERoaipKSEvTo0QPx8fFYvHgxXnnlFfTu3VtR9/79+4p7qC9evAigNFlv0qQJmjRpgr59+1bYV9OmTeHt7Y2oqCj0798f1tbWAEqT7uzsbGRnZ2PVqlVKbUJDQxEdHY2ePXti+vTpeO655/D06VPcunULe/fuRUREBJo3b662v3nz5uGPP/7ASy+9hHnz5sHKygoRERF49OgRgOrN4nt7ewMA1q9fDxsbG1haWsLDwwNxcXEIDw/HyJEj4enpCUEQsHv3bjx48ACDBg2qcj+kO0y6iYiIiIhIVDt27MDixYuxfv16LF68GK6urggKCsLChQuV6l2+fFnxnGq5qVOnAiidVT5y5EilfQ0cOBAXL15Uum+7RYsW8PLyQnJyslI5ALi4uCA+Ph5LlizBV199hb///hs2Njbw8PDAyy+/DHt7e419de7cGdHR0fjkk08wYcIE2NvbIyAgAH379sWcOXNgZ2dXabzleXh4YM2aNfjmm2/Qr18/FBcXY/PmzfDz80OjRo2wYsUK3L17F+bm5njuueewZcsWvPPOO1Xuh3RHIshvliAiIiIiIiK98/f3x61bt3Dt2jWxQyED4Ew3ERERERGRngQHB+P555+Hm5sbsrOz8fPPPyM6OhqRkZFih0YGwqSbiIiIiIhIT4qLi/H5558jPT0dEokE7du3x//93/9h/PjxYodGBsLLy4mIiIiIiIj0hI8MIyIiIiIiItITJt1EREREREREesJ7unWgpKQEd+/ehY2NDSQSidjhENV7giAgPz8frq6u1Xr+JdUcx0Ui48JxkYhIPEy6deDu3btwc3MTOwwiKic1NRXNmzcXO4x6ieMikXHiuFj7ZGRk4PTp0zh9+jTOnDmDM2fOICsrC0DpM6BPnz4Nc3PzSvcjk8kQFhYGAAgJCVHbJjkrGfmyfMXrosIi7Nu8DwAwZOIQmElVUwcbcxt4OXhVqZ/qxidWG8an3zbJWclos7aNSjsppJiHeQCAL/AFClGoUufatGtKf3/Gikm3DtjY2AAo/R+Zra2tyNEQUV5eHtzc3BTfTTI8jotExoXjYu3l5ORkkH7UJT5lk57em3urTXqA2pP4GCN1JzrkEtMTtTrRUduVPX5DtjUkJt06IL900tbWlv+4JDIivKxZPBwXiYwTx8Xazc3NDe3atUNUVJTO910fEh9jwxMd9QeTbiIiIiIiI/X555/jhRdewAsvvAAnJyfcunULHh4eYodFOsATHfUHk24iIiIiIiO1ePFisUMgI1X20vSKLkuva5ej10ZMuomIiIiIiGqRlNwUjNoxSvG6ssvSeTm6uJh0ExERERFR7ZGcDOTnA0XPZneRmAiY/ZPa2NgAXnU7wXxU+KhK9Xk5uriYdBMRERERGTn5pcR3M+4qlZe9lLheXEacnAy0+WfxMakUmFc6u4vevYHCMrO7167V+cSbag8m3URERERERkxplesc5W3lLyWu85cR52s5Y6ttPSIDYNJNRERERGTEqnJpMC8jJmOSnJWMnEfPzhTVuysz/sGkm4iIiIiIiCqkKYHWlDzLr9CoaJG3a9Ou6T9wI8Ckm4iIiIiIiDSqLIFWd1uDNldd1JcrM0zEDoCIiIiIiMhoJSeXro4ul5hYWlaZ69er3sZIVZYc15fkubqYdBMREREREakjXy29d+9nZb17l5ZVlEQnJwOdO1etDdVZTLqJiIiIiEj3jHx2NzE9EclZlcRX0Sro1dnGVdXrJSbdRERERESkG2UvqTbC2d3r2c/i6725N9qsbVN54k1UQ0y6iYiIiIhIN9TN5BrR7K66e495PzKUT5aQzjHpJiIiIiKqT5hgEaD8d9C5s1FdkVDX8JFhRERERERGRhAEFBYW4sSJEzh87jBME0xLyx8LKEEJACA7OxuSBAlMYapoV1RYBJlMprI/2dWrz3739QXi44FWrZTaSSFValP2dfltZZXtU1ZU9Kwf6T9tiooANTEpxVdmu7r4n3VWBPyzX8X+y/1eUZ9Fhc/ikx+TpvesbH9q+6rouIqKKm1Tk/e8pKhEY1117dQdoyzn2TO3ZVIpkJOj8XjksWrqR93+K2sjr6Mp5oqOv7aRCIIgiB1EbZeXlwc7Ozvk5ubC1tZW7HCI6j1+J8XHz4DIuPA7WfvIZDKEhYVhz549OH/+vNbtFi1apL+giESyAivwGI9Vys++fxZdXbqKEFHV8PJyIiIiIiIiIj3h5eVEREREREZGKpUiJCQEISEhSExPRO/Nz54TLYUUszEbQOkMYCEKFduGTByCLs5dVPYni4/H19HRAIBZK1bA/PBhoMuzeuX7qKyfso5PPK7oU6WfwkLg+HGlvtSRyWT4+uuvS9uNHw9zNzf1FRMTFc/Mlkml+Hr2bOW+FEGp7zM+NR7RP0UrHVPZ+DX1p7avio4rMRGy/v0rbFOT9zxyeCQm/T5J63bqjrGyvwmlw/knVk39qNt/ZW3k7QCovA/lj0nT+1BbMOkmIiIiIjIyEokE5ubmAAAzqZnGpKPwn//kzKRminZK7O0Vv5oXFsLc3h4oU6+iPtT1U5ZSn2bP0gvzwsLSRNPMTKmvypi7uak/Bvn+C1XjUPRVtp6afZhJn8UnPyaN75mG/rQ6rnLt1LWpyXtuYmai9d+EvC+VYyz/WVVwPOpiLduPuv1X1kZeR15el/HyciIiIiKiuq7Momk4fx7w8hIvFjIONjYVvyadqXVJd3h4ODw8PGBpaQkfHx8cO3aswvoxMTHw8fGBpaUlPD09ERERobHu9u3bIZFIMHLkSB1HTUSkPxwXiYioSsom4FR/8USMwdSqpHvHjh2YOXMm5s2bh4SEBPTp0wdDhgxBSkqK2vo3b97E0KFD0adPHyQkJOCzzz7D9OnTsWvXLpW6t2/fxieffII+ffro+zCIiHSG4yIRERkVdbOlRjSDamOuGou6snqHJ2L0qlYl3atWrcKkSZMwefJktGvXDmvWrIGbmxvWrVuntn5ERARatGiBNWvWoF27dpg8eTLee+89rFy5UqlecXEx3n77bSxevBienp6VxlFQUIC8vDylHyIiMXBcJCIio1I2eTt+HLh2zahmUFs1fhbf8YnHcW3aNXg5GE98VDfVmqRbJpPh7Nmz8Pf3Vyr39/dHbGys2jZxcXEq9QcPHoz4+HgUllnYIDQ0FE2aNMGkSZPK70KtsLAw2NnZKX7cNK2uSESkRxwXiYjIqHXpYlQJd3ldnLvoL+HWNLtvRLP+ZDi1JunOzMxEcXExnJyclMqdnJyQnp6utk16erra+kVFRcjMzAQAnDhxApGRkdiwYYPWsYSEhCA3N1fxk5qaWsWjISKqOY6LREREelZRklzRNi+v0vuk5Yxw1p8Mp9Y9MkwikSi9FgRBpayy+vLy/Px8jB8/Hhs2bICjo6PWMVhYWMDCwqIKURMR6Q/HRSIiIj3x8ipNlnNygH37SsuOHy99BFtlCXTZS+27dKnSY9OMTWX3vfO++IrVmqTb0dERpqamKrM3GRkZKrM2cs7Ozmrrm5mZwcHBAZcvX8atW7fw6quvKraXlJQAAMzMzHD16lW04qICRGSkOC4SEREZgJcXIJM9S7preQJdHV4OXrg27RpyHuVg3+bS9+H4xOMwk5rBxtxG7WX62iTiNuY2yJfl6zxeY1Nrkm5zc3P4+PggOjoao0aNUpRHR0djxIgRatv4+fnhjz/+UCqLioqCr68vpFIp2rZti4sXLyptnz9/PvLz8/HNN9/wnkQi0oqnpyfOnDkDBwcHpfIHDx6ga9euuHHjhl765bhIREREhuLl4AWZjQz7UJp0d3HuAvMKTj5UlKgDUCTr59LO6T94kdWapBsAgoODERAQAF9fX/j5+WH9+vVISUlBYGAggNJ7Cu/cuYOtW7cCAAIDA7F27VoEBwdjypQpiIuLQ2RkJLZt2wYAsLS0hLe3t1IfjRo1AgCVciIiTW7duoXi4mKV8oKCAty5c0evfXNcJCKq+6py6S4v8yVjUtVEva6qVUn36NGjkZWVhdDQUKSlpcHb2xt79+6Fu7s7ACAtLU3p2bQeHh7Yu3cvgoKC8P3338PV1RXffvstXn/9dbEOgYjqkN9//13x+4EDB2BnZ6d4XVxcjEOHDqFly5Z6jYHjIhFR3SefMZRfhltUWFThzGGdpu3q31wlnIxIrUq6AWDq1KmYOnWq2m1btmxRKevbty/OndP+kgV1+yAiUmfkyJEAShcge+edd5S2SaVStGzZEl9//bXe4+C4SERU95VNpmWyejxzKF/YLD8fKCpSXtzM7J/UxsaGq4STUal1STcRkbGQLzDm4eGBM2fOVGm1byIiIqomeUJdjxc3ayBtUKX6vO1AXEy6iYhq6ObNm2KHQERERPVIC7sWWt1yANST2w6MHJNuIiIdOHToEA4dOoSMjAzFDLjcpk2bRIqKiIiI6ireclB7MOkmIqqhxYsXIzQ0FL6+vnBxcYFEIhE7JCIiIiIyEky6iYhqKCIiAlu2bEFAQIDYoRAREVVLTe755f3C1cP3vP5g0k1EVEMymQw9e/YUOwwiIqJqK/9YMqDi+4TlDHq/cHJy6arligCLnv2emPhs9XI5I1/FvFa85wZQH04+MOkmIqqhyZMn45dffsGCBQvEDoWIiKjavLIBlMlpZUX45y5hoEs6YF4+czBkUpucDLRpo1wmlQLz5pX+3rs3UFio2u7aNaNPvMuqj/dmqzv5AFR+AqI2nXxg0k1EVENPnz7F+vXrcfDgQXTq1AlSqVRp+6pVq0SKjIiI6oLkrGSV2VC5xPRE3axUbexJbX5+5XV02a4OqsrfEWDYpFZdP3XpBASTbiKiGrpw4QK6dOkCALh06ZLSNi6qRkRENZGclYw2a5WTYSmkmIfSZLj35t4ohHIyfG3ataonS3U5qS17WXotvyS9uqrzdwRU82+JVDDpJiKqocOHD4sdAhER1VHlL7nVV5s6KyUFGDXq2Wtjmr03oOr+TfBvSTdMxA6AiKiu+Ouvv3DgwAE8efIEACAIgsgRERER1XOPHlW9TW2YvadahUk3EVENZWVl4aWXXkKbNm0wdOhQpKWlAShdYG3WrFkiR0dERMYiNjYWU6ZMQfv27WFjYwMbGxv4+Phg+fLleFSd5JCMU3IycO5c6eXrcomJpWXnzpVup3qFSTcRUQ0FBQVBKpUiJSUF1tbWivLRo0dj//79IkZGRETGIC8vDxMmTECvXr2wceNGXLlyBQ8fPsTDhw9x7tw5hISEoFu3brhz547YoVJNyRek8/EpvXxdrnfv0jIfn9LtTLzrFd7TTURUQ1FRUThw4ACaN2+uVO7l5YXbt2+LFBURERmDhw8f4qWXXkJ8fDwAYMSIEXj77bfh4eGBBw8e4JdffsHmzZuRlJSEN954A7GxsVyEszbT9tJ0HV3CLl+RXN1K5LXpkVpK5AvfqVv0rpYudMekm4iohh49eqQ0wy2XmZkJCwsLESIiIiJjMXr0aMTHx8PU1BRbt27FuHHjlLYPHDgQFhYWiIiIwMmTJ7Fv3z4MHTpUpGhJneSsZOQ8ylG8NpaktuyK5JpWIq91q4+XfXSdpkXvauFCd7y8nIiohl588UVs3bpV8VoikaCkpARfffUV+vfvL2JkREQkpl9++QV79+4FACxfvlwl4ZabP3++4nd93JaUnJWMxPRExevE9EScSzuH5Cxe4lwZeWLbe/OzS8V7b+4Nn/U+aLO2jajvoTYri9e61ce1uQKgFi50x5luIqIa+uqrr9CvXz/Ex8dDJpNh9uzZuHz5MrKzs3HixAmxwyMiIpGsWLECANChQwcEBwdrrNesWTPY2toiLy8PKSkpOo1BnjTWmZlQA6ssaa11SS2JgjPdREQ11L59e1y4cAHdunXDoEGD8OjRI7z22mtISEhAq1atxA6PiIhEcPHiRZw/fx4A8NFHH8HEpOJ/dtvZ2QEApFKpTuNg0kgkPs50ExHpgLOzMxYvXix2GEREZCQOHz6s+F2be7Szs7MBAE2aNNFbTAaXmAjY29e6+29JfxLTE2HfwL7eXV3BpJuISAeePn2KCxcuICMjAyUlJUrbhg8fLlJUREQklgsXLgAAbGxs4O7uXmHdmzdvKp7T3aVLF32Hpl/Xrz/7Xb74VS1c+MoY1JUE9Xr2s78J+a0N9e22BibdREQ1tH//fkyYMAGZmZkq2yQSCYqLi0WIioiIxCT/f0Ljxo0rrRsVFaX4/cUXX9RbTAahbpGrWrjwlVjqYoKq7haG+nZbA+/pJiKqoWnTpuHNN99EWloaSkpKlH6YcBMR1U+mpqYAgIKCggrrlZSUYN26dQCArl27om3btnqPjYxXrUpQy17VQBXiTDcRUQ1lZGQgODgYTk5OYodCRERGwsPDAwCQnp6OjIwMNG3aVG297777TrHg2ieffKIoFwQBhYWFKCosghTKi6uVfV1+GwAUFRZBJpMpfpf+85+6Noq6RUWlz0UuQ1bmtUzTAm9FRcA/fQGArKhItU25OurIyu5DXd3qxldSotSuqsek9funJs4K+9LwnhQVPnv/5P2o9FGmrrxOZfFV5+9IXd+yq1ef/e7rC8THA5UsGluVY9L0/gkV9mD8JIIg1PZjEF1eXh7s7OyQm5sLW1tbscMhqvcM/Z1877330KtXL0yaNEnvfdUWHBeJjAu/k4Z38OBBDBo0CAAQEhKCZcuWqdTZvn07AgICUFRUBH9/fxw4cECxTSaTISwszGDxEhmzWStWoOHjx6Uvzp4FunYVN6Aq4kw3EVENrV27Fm+++SaOHTuGjh07qjzuZfr06SJFRkREYhk4cCD8/PwQFxeHsLAwZGZmYvTo0bC3t8fNmzfx008/4bfffgMAdO7cGb/++qu4AROR3tS6me7w8HB89dVXSEtLQ4cOHbBmzRr06dNHY/2YmBgEBwfj8uXLcHV1xezZsxEYGKjYvmHDBmzduhWXLl0CAPj4+GDZsmXo1q2b1jHx7DGRcTH0d3Ljxo0IDAyElZUVHBwcIJFIFNskEglu3Lih1/45LhJRZfidFEdqaioGDhyIa9euaazz5ptv4ocffoC9vb1Sufzy8sT0RPTe3FtpmxRSzMZsAMAKrEAhCpW2H594HF2cuwCAor2mNoq6iYmlq42XIZNK8fXs0jazVqyAeaFyP6U7OA6UWXFdFh+Pr6OjlduUq6NOfGo8on8qbTdo/CD4uvkqV6hufJGRQJkr0ap6TFq/f2rirLAvDe9J2fdB3o9KH/KuyvxtVBZfdf6O1B2fyud7+HCVPtvKjknT+zf3iy9gIX//ONOtXzt27MDMmTMRHh6OXr164YcffsCQIUOQlJSEFi1aqNS/efMmhg4diilTpuCnn37CiRMnMHXqVDRp0gSvv/46AODIkSMYO3YsevbsCUtLS6xYsQL+/v64fPkymjVrZuhDJKJaaP78+QgNDcXcuXNhYmLY9Sk5LhIRGS83NzecOXMG33zzDXbu3Im//voLJiYmaNasGfz8/DBhwgT0799fbVuJRAJzc3OYSc3UJkNyhf/8V5aZ1Azm5uaK38tvL9tGUdfMrPTxXhqYFxaqT1DNzIB/+lK8Lt+mfB017BvYK/1uXr5+deMzMdHYTptj0vr9qyROlb40vCdm0mfvn7wflT7K1FX3t6Euvur8HWk8vrLHpMVnW5Vj0vT+SVRr1iq1KuletWoVJk2ahMmTJwMA1qxZgwMHDmDdunVq73mJiIhAixYtsGbNGgBAu3btEB8fj5UrVyr+cfnzzz8rtdmwYQN27tyJQ4cOYcKECWrjKCgoUFqJMi8vTxeHR0S1lEwmw+jRow2ecAMcF4mIjJ2trS0WLFiABQsWiB2KUWvVuJXa38nI2NhU/JrUqjWPDJPJZDh79iz8/f2Vyv39/REbG6u2TVxcnEr9wYMHIz4+HoUazng9fvwYhYWFFT5TMSwsDHZ2doofNze3Kh4NEdUl77zzDnbs2GHwfjkuEhERkUGVXan8/HnAq/Y+P9yQas1Md2ZmJoqLi1UeyePk5IT09HS1bdLT09XWLyoqQmZmJlxcXFTazJ07F82aNcPAgQM1xhISEoLg4GDF67y8PP4Dk6geKy4uxooVK3DgwAF06tRJZSG1VatW6aVfjotERER1i4256syxujKjUMmjwuiZaiXdRUVFOHLkCK5fv45x48bBxsYGd+/eha2tLRo2bKjrGJWUXaAIKF1konxZZfXVlQPAihUrsG3bNhw5cgSWlpYa92lhYQELC4uqhE1EddjFixfx/PPPA4Bi8TG5isYnXeG4SERERkPd5ca8BFlrZS+tPz7xOOwb2MPLoXbPJteqEwl6UuWk+/bt23j55ZeRkpKCgoICDBo0CDY2NlixYgWePn2KiIgIfcQJR0dHmJqaqszeZGRkqMzayDk7O6utb2ZmBgcHB6XylStXYtmyZTh48CA6deqk2+CJqE47fPiwKP1yXCQiIqNTdvbz+HHA3p6XIFdTF+cu6hcbq2Xq4omEqqryPd0zZsyAr68vcnJyYGVlpSgfNWoUDh06pNPgyjI3N4ePjw+i/1miXi46Oho9e/ZU28bPz0+lflRUFHx9fZUu//zqq6+wZMkS7N+/H76+vuV3Q0Sklb/++gsHDhzAkydPADybQdYXjotERGTUunRhwk1Kujh3qXcJN1CNpPv48eOYP3++ylkXd3d33LlzR2eBqRMcHIyNGzdi06ZNuHLlCoKCgpCSkqJ4vmxISIjSyrqBgYG4ffs2goODceXKFWzatAmRkZH45JNPFHVWrFiB+fPnY9OmTWjZsiXS09ORnp6Ohw8f6vVYiKjuyMrKwksvvYQ2bdpg6NChSEtLAwBMnjwZs2bN0mvfHBeJiKgilV3GW98u8yUSQ5UvLy8pKUFxcbFK+d9//w0bPd+vMXr0aGRlZSE0NBRpaWnw9vbG3r174e7uDgBIS0tDSkqKor6Hhwf27t2LoKAgfP/993B1dcW3336reCwOAISHh0Mmk+GNN95Q6mvhwoVYtGiRXo+HiOqGoKAgSKVSpKSkoF27dory0aNHIygoCF9//bXe+ua4SEREFfFy8MK1adeQ8ygH+zbvA1B6ia+Z1Aw25jb1ctaxKnjSgnShykn3oEGDsGbNGqxfvx5A6cI7Dx8+xMKFCzF06FCdB1je1KlTMXXqVLXbtmzZolLWt29fnDt3TuP+bt26paPIiKi+ioqKwoEDB9C8eXOlci8vL9y+fVvv/XNcJCKiing5eEFmI8M+lCbddeVeYUMw5pMW2iT8te6kgDaTuLVwYb4qJ92rVq3CgAED0L59ezx9+hTjxo1DcnIyHB0dsW3bNn3ESERk1B49egRra2uV8szMTK7oTUREVMsZ60kL+QmBfFk+igqLjO6kQLV4eQHXrgH5+UBREbCv9Jhw/DhgZlaacNfCdQKqnHQ3a9YMiYmJ2L59O86ePYuSkhJMmjQJb7/9ttLCakRE9cWLL76IrVu3YsmSJQBKrwAqKSnBV199hf79+4scHRERERmMtrOwOpqtlSfVMpnxnRSoNnlSLZM9S7q7dAFq8TFVKekuLCzEc889h//85z+YOHEiJk6cqK+4iIhqja+++gr9+vVDfHw8ZDIZZs+ejcuXLyM7OxsnTpwQOzwiIiIylMpmaoFaO1tL1VelpFsqlaKgoAASiURf8RAR1Trt27fHhQsXsG7dOpiamuLRo0d47bXX8NFHH8HFxUXs8IiIiMiQ6uBMLdVMlS8v//jjj/Hll19i48aNMDOrcnMiojrJ2dkZixcvFjsMIiIiKqtBg6q3qYULdZFxq3LWfOrUKRw6dAhRUVHo2LEjGpT7Q969e7fOgiMiqg02b96Mhg0b4s0331Qq//XXX/H48WO88847IkVGRES1XXVWn651K1brU4sWzy73BjRf8i1XRy/9ru7fBP+WdKPKSXejRo2UnudKRFTfLV++HBERESrlTZs2xfvvv8+km4iIqq3sCtVy6laqlqv2itXVnd2tDbPCZZPoenrJd1X/joAa/C2Riion3Zs3b9ZHHEREtdbt27fh4eGhUu7u7o6UlBQRIiIiorqkfOKjl5Wqyy4AJqenWeHkrGSl5A8oTQDlEtMTVRNAIQvVSv9qw0kBAzHI3xGpVe2bsu/fv4+rV69CIpGgTZs2aNKkiS7jIiKqNZo2bYoLFy6gZcuWSuXnz5+Hg4ODOEERERFVVfkEWg+zwslZyWizto1KuRRSzMM8AEDvzb1RiEKVOtfio+AlKfP/VW1OCgDAuXPKbeQSE1XbyNvVwUvMSTxVTrofPXqEjz/+GFu3bkVJSQkAwNTUFBMmTMB3330Ha2trnQdJRGTMxowZg+nTp8PGxgYvvvgiACAmJgYzZszAmDFjRI6OiIjIeJSf4a5SW1cHwKXrs4LKTgokJwNtyiX4UikwrzS5R+/eQKFqcg+gdNafiTfpiElVGwQHByMmJgZ//PEHHjx4gAcPHuDf//43YmJiMGvWLH3ESERk1JYuXYru3bvjpZdegpWVFaysrODv748BAwZg2bJlYodHRERUP+VXP8GvUVuicqo8071r1y7s3LkT/fr1U5QNHToUVlZWeOutt7Bu3TpdxkdEZNQEQUBaWho2b96MpUuXIjExEVZWVujYsSPc3d3FDo+IiIjI8JKTlVeMl1N3SX89uJy/ykn348eP4eTkpFLetGlTPH78WCdBERHVFoIgwMvLC5cvX4aXlxe86vj/NIiIiKh+KbvwXUUL3ilWOy9/Wb82l/TX8cv5q5x0+/n5YeHChdi6dSssLS0BAE+ePMHixYvh5+en8wCJiIyZiYkJvLy8kJWVxYSbiIjqBHmSpVWCVZdpM1tbx2dpyy98V9mCd9emXYNXdS7Nr+OX81c56f7mm2/w8ssvo3nz5ujcuTMkEgkSExNhaWmJAwcO6CNGIiKjtmLFCnz66adYt24dvL29xQ6HiIio2somWVolWHU18U5JAUaNeva6otnaOjxLW9WF72qyUF5dVuWk29vbG8nJyfjpp5/wv//9D4IgYMyYMXj77bdhZWWljxiJiIza+PHj8fjxY3Tu3Bnm5uYqY2F2drZIkRERkbF4/Pgx9u3bh4MHDyI+Ph43btxAXl4eLC0t0apVKwwbNgzBwcGiP2qyKklTnU6wHj3Svm4dn6WlmqvWc7qtrKwwZcoUXcdCRFQrrVmzRuwQiIjIyA0dOhQxMTEq5Q8fPsT58+dx/vx5bNmyBUePHkWrVq1EiJCI9KXKSXdYWBicnJzw3nvvKZVv2rQJ9+/fx5w5c3QWHBFRbfDOO++IHQIRERkxQRBw/vx59OrVC4MHD0aXLl3g4uKCoqIi3Lp1Cz/99BP++9//4u7du5g6dSpv2STtye87V3fPeR2/37w2qXLS/cMPP+CXX35RKe/QoQPGjBnDpJuI6qXi4mL89ttvuHLlCiQSCdq3b4/hw4fD1NRU7NCIiEhkhYWFOHfuHDw8PFS29ejRA2PGjMH777+PDRs24ODBg8jNzYWdnZ0IkZKokpOBnJxnrytLnsuuEq7pnnMN95snZyUj59GzvuQL5dWLBfJEUOWkOz09HS4uLirlTZo0QVpamk6CIiKqTf766y8MHToUd+7cwXPPPQdBEHDt2jW4ubnhv//9Ly8TJCKq58zNzdUm3GWNHj0aGzZsQElJCe7cucOkuxJ1LmmUJ9BVSZ61uZdcTR35YnmaFsozhgXy1H2+9g3sRY+ruqqcdLu5ueHEiRMqA8eJEyfg6uqqs8CIiGqL6dOno1WrVjh58iQaN24MAMjKysL48eMxffp0/Pe//xU5QiIiMiY5OTnIycnBkydPIAgCAODixYuK7RYWFmKFVnPXrz/7XU+XOdeGpLHKKkugdbhYW2UL4Im9QF5Fn2+t/GxRjaR78uTJmDlzJgoLCzFgwAAAwKFDhzB79mzMmjVL5wESERm7mJgYpYQbABwcHLB8+XL06tVLxMiIiMhY7Nu3D5s3b0ZMTAwyMjI01jM1NYWbm5sBI9Oh5GSgc+cqXeZcHcaeNFLNVPT51dbPtspJ9+zZs5GdnY2pU6dCJpMBACwtLTFnzhyEhIToPEAiImNnYWGBfDVnoB8+fAhzc3MRIiIiImORk5ODsWPHar04mpeXV+39f0dFs7F8rBbVYyZVbSCRSPDll1/i/v37OHnyJM6fP4/s7Gx8/vnn+oiPiMjovfLKK3j//fdx6tQpCIIAQRBw8uRJBAYGYvjw4WKHR0REIikqKsLgwYMVCffIkSOxfft2XL16Ffn5+SguLlb8f6N58+YAgOeff17vcV3Pvl55JSLSmSon3XINGzbECy+8ABsbG1y/fh0lJSW6jEuj8PBweHh4wNLSEj4+Pjh27FiF9WNiYuDj4wNLS0t4enoiIiJCpc6uXbvQvn17WFhYoH379tizZ4++wieiOujbb79Fq1at4OfnB0tLS1haWqJXr15o3bo1vvnmG733z3GRiMg4RUZG4syZM4rf9+zZg9GjR6NNmzZo2LAhTExK/yl+9epV/P333wD0l3SXTbQ7/9AZyVnJeumHiFRpnXT/+OOPWLNmjVLZ+++/D09PT3Ts2BHe3t5ITU3VdXxKduzYgZkzZ2LevHlISEhAnz59MGTIEKSkpKitf/PmTQwdOhR9+vRBQkICPvvsM0yfPh27du1S1ImLi8Po0aMREBCA8+fPIyAgAG+99RZOnTql12MhototLy9P8XujRo3w73//G9euXcPOnTvx66+/4urVq9izZ4/eV5/luEhEZLx+++03AECbNm3w3nvvaay3fv16xe/aJN1lE2htZ63L3wtbW++NJaqNtL6nOyIiAu+//77i9f79+7F582Zs3boV7dq1w7Rp07B48WJs3LhRL4ECwKpVqzBp0iRMnjwZALBmzRocOHAA69atQ1hYmNqYW7RooThZ0K5dO8THx2PlypV4/fXXFfsYNGiQ4n70kJAQxMTEYM2aNdi2bZvejoWIajd7e3ukpaWhadOmGDBgAHbv3o3WrVujdevWBo2D4yIRkfGST0g1adJEY52LFy9i7dq1itddunQBAAiCgEL5ImTllH2UUs6jHMU6SxUpKixS/C6FFEWFRWrbFRUWQQqpol7ZNurqKu2jqAgy6bN6ZX9HUREgkyntv6zK+irbn3wfmtoo4ioqKn0EVxka4yuvpESpbYXt/jm28sq+N5V+Rv/EWtn7p65NhfGpaaf1+weofF5a/U0UQfv3Tk2c6uKr7SSC/DkFlXBwcMCRI0fQsWNHAMCHH36IjIwMxezIkSNHMHHiRNy8eVMvgcpkMlhbW+PXX3/FqFGjFOUzZsxAYmIiYmJiVNq8+OKLeP7555Uu79yzZw/eeustPH78GFKpFC1atEBQUBCCgoIUdVavXo01a9bg9u3bamMpKChAQUGB4nVeXh7c3NyQm5sLW1tbXRwuEdVAXl4e7Ozs9PqdtLOzw8mTJ9GuXTuYmJjg3r17Ff6jSh84LhKRtgwxLpKqnj17Ii4uDjY2Nvjrr7/QtGlTpe2XL1/GkCFDFMm5m5ub4kolmUym9uQpUX20AivwGI9x9v2z6OrSVexwqkzrme4nT54oDdKxsbFKl8l4enoiPT1dt9GVkZmZieLiYjg5OSmVOzk5aew3PT1dbf2ioiJkZmbCxcVFY52KjiUsLAyLFy+u5pEQUV0wcOBA9O/fH+3atQMAjBo1SuNqs3/++adeYuC4SERk3IYOHYq4uDjk5+djwIABmD9/Pry8vJCZmYn//Oc/2LhxI1q0aAEHBwdkZWUZZBE1IjI8rZNud3d3nD17Fu7u7sjMzMTly5fRu3dvxfb09HS937sIlK6eXpYgCCplldUvX17VfYaEhCA4OFjxWj6jQ0T1x08//YQff/wR169fR0xMDDp06ABra2tRYuG4SERknGbOnImdO3fi/PnzuHz5MsaOHau0vVu3bvj555/Rtm1bAMr3c0ulUo2P441PjUf0T9EAgEHjB8HXzbfSWMq2WYEVODzxMLo4d1Gpl5ieiN6bS/+NL4UUszFb0aYQype7H594XHkfiYmQ9e+Pr2eXtpm1YgXM5ZfIHz8OdOmitP+yKuurbH/yfWhqo4grMbH0WeFlyKRS9fGVFxkJTJqkXbt/jq08mUyGr7/+urTN+PEwr+j/i//EqrEfdX2UOb6qtNP6/QNUPi+t/ibSofS+a/Wel4lTXXzq/h5qE62T7gkTJuCjjz7C5cuX8eeff6Jt27bw8fFRbI+NjYW3t7deggQAR0dHmJqaqsy0ZGRkqMzIyDk7O6utb2ZmBgcHhwrraNonUPpMXgsLi+ocBhHVEVZWVggMDAQAxMfH48svv0SjRo0MGgPHRSIi49awYUMcO3YMixcvxs6dO3H37l00btwYnTp1wrhx4xAQEIBLly6huLgYwLP7uYHSk5+arqCyb2Cv9Ls2z/U2kz77Z38hCmEmNVPbzkxqpjbBKfznv/J1lfZhZgaUSajMCwufJVhmZoC5ucb9V9ZX2f7U7aNsG0Vc5eIpTym+8kxMNLZVaffPsVXE3M2t4s9JTazq3r/K2mjTTuv3T0NddW3K7tvcDNq/d2WPRYs+ayutVy+fM2cOJk+ejN27d8PS0hK//vqr0vYTJ06onL3TJXNzc/j4+CA6OlqpPDo6Gj179lTbxs/PT6V+VFQUfH19If3nJn5NdTTtk4iovMOHDxs84QY4LhIR1QY2NjZYuXIlbt26BZlMhvT0dERFReHdd9+FqakpOnfurHhW98iRI7XaZ6vGrdT+XmEc5jYVviYi/dF6ptvExARLlizBkiVL1G4vn4TrQ3BwMAICAuDr6ws/Pz+sX78eKSkpitmmkJAQ3LlzB1u3bgUABAYGYu3atQgODsaUKVMQFxeHyMhIpdV3Z8yYgRdffBFffvklRowYgX//+984ePAgjh8/rvfjIaK6obi4GFu2bMGhQ4eQkZGBkpISpe36uqcb4LhIRETaKZucn//gPLwcvESMhqh+0TrpNgajR49GVlYWQkNDkZaWBm9vb+zduxfu7u4AgLS0NKVn03p4eGDv3r0ICgrC999/D1dXV3z77beKx+IApatKbt++HfPnz8eCBQvQqlUr7NixA927dzf48RFR7TRjxgxs2bIFw4YNg7e3d4X3Pusax0UiIqoqbWfHiUg3alXSDQBTp07F1KlT1W7bsmWLSlnfvn1x7ty5Cvf5xhtv4I033tBFeERUD23fvh3/+te/MHToUFH657hIREREZLy0vqebiIjUMzc3R+vWrcUOg4iISFw2FdwnXtE2ojqOSTcRUQ3NmjUL33zzjeLRW0RERPWSlxdw/vyz18ePA2fPAteulW7TkcoWgeMicbVbRZ9fbf1sq3x5+ZMnT2BlZaV2W1paGlxcXGocFBFRbXL8+HEcPnwY+/btQ4cOHRSrgMvt3r1bpMiIiIgMrFWZ+8W7dKn0UVrV4eXghWvTriHnUQ72bd4HoPT50GZSM9iY29TOReIquxJA3XZtrh5QU8fYT1po+nztG9jXzs8W1Ui6n3/+efzyyy/o2rWrUvnOnTvx4Ycf4v79+zoLjoioNmjUqBFGjRoldhhERET1hpeDF2Q2MuxDaVLWxbmLVs8rN1peXqVXBOTkAPtKjwnHj5c+v9rGRv2VAvI2+flAUZHW7WrDSYu69vlWOekeNGgQevbsiUWLFmHOnDl49OgRpk2bhl9//RXLly/XR4xEREZt8+bNYodAREREtZ2XFyCTPUuetblSQJ5UV7FdXUtqjV2Vk+7vvvsOw4YNw8SJE/Hf//4Xd+/eha2tLc6cOYP27dvrI0YiIiIiIiKiWqlajwzz9/fHa6+9hnXr1sHMzAx//PEHE24iqle6du2KQ4cOwd7eHs8//3yFz+au7PFcRERERFR3VTnpvn79OsaNG4f09HQcOHAAMTExGDFiBKZPn44vvvhCZQEhIqK6aMSIEbCwsAAAjBw5UtxgiIiIdKQqi2iJveCWXjVooH1dPg6NKlHlpLtLly4YNmwYDhw4gEaNGmHQoEEYOnQoJkyYgOjoaCQkJOgjTiIio7Jw4UK1vxMREdVm8kW28mX5KCosUlloS85YFtzSmxYtni1SBqhfqAzQvMhZHVHVEyul9fP1E0wtVuWkOzw8HAEBAUplPXv2REJCAmbOnKmruIiIapUHDx5g586duH79Oj799FM0btwY586dg5OTE5o1ayZ2eEREVMsJgoCsrCwAQGFhoaI8KysLUqkUjRo1gpmZ6j/t8/LyIJPJ1LaxtraGtbW1Shu3Bm54KDxEIZ61cZO6QSqVwszMDI0aNVJpU1xcjJycHLX9AEDjxo1hYmJS9QNX49GjR3jy5InavszNzWFra6uTfoTWrZGdnQ1BEJT7cit9L2xtbdUuPpafn4+CggK18VlaWqJhw4YqbQoKCpCfn6+2jYmJCRo3bqzSpqSkBNnZ2QCq9jeRm5uLwsJCrf4m5Cdh7ufdx5OnT1BcVIy43XEAgP+89h+YmpnCzNQMNjY2z07EZCerf0MrUuZqgZycHBQXF6uNr2HDhrC0tKz6/kUmEQRBEDuI2i4vLw92dnbIzc3V2ZeciKrP0N/JCxcuYODAgbCzs8OtW7dw9epVeHp6YsGCBbh9+za2bt2q9xiMDcdFIuPC72Tt9+9//xs///wzAEAikSjWU0pKSoIgCOjduzemT5+u1CY1NRWzZs3S2MbKygpbtmxRWZdk6tSpyMzMVNsGABYtWqSyntPmzZuxb98+jW2GDx+O8ePH41zaOfis91E5PimkmId5AIAv8IVSwi939v2z6Ny0M9555x3IZDK1fUkkEqxZswYuLi7AuXOAj3JfMqkUYfNK+wn54guYF6r2U9rZWRzJy0N4eLjG98/b2xuff/65UrMHDx4gMDAQJSUlatuYmppi06ZNsLKyUmoXEhKC69eva3z/goOD0aNHD6U2u3fvxvbt2zXG17dvX3z00UdKbW7duoXZs2drbNOwYUNERkaq/E188MEHyMnJ0Rjf0qVL0aZNm2cNkpOxZ+tWnDx5EjAxgXXPngCAx7GxQEkJAGDAgAEYPHiw0tUCCQkJCAsL0xifm5sbvv76azUfmHGr1kJqQOmBp6SkQCaTKcokEgleffVVnQRGRFRbBAcH491338WKFStgU+ZM7ZAhQzBu3DgRIyMiorqiQQX3GJuYmKidPbWp5F5jKysrtQuBNmjQAJmZmRrbqdtvgwYNKlxUVF181WFqagoLCwulHKQsQRCexVeTe61tbNDwn+RQHYlEovZ9UHflQFny+Mur7P1Rt72yvwl18VXWj6a/CWtra+Tk5Ggfn5cXnrZvj1vXrgEA5KdobjVqpEjUCzt2BLp2VWpW0TFpE7+xqnLSfePGDYwaNQoXL16ERCJRvGnyD6e4uFi3ERIRGbkzZ87ghx9+UClv1qwZ0tPTRYiIiIjqGk9PT43bSkpK1G5v1KgRbGxskJ+veo+tRCJB69at1e6vdevW+Pvvv1GiJuk0MzODq6ur2vjkM81Vjb+qWrVqhcTERLXbGjdu/Cwx8/JS3JcdFxeH3377rXTW9Z+6C/r3V8y6BgcHw8nJqXTDPzOvHv9czq+ORCJRe0zm5uZwdXXF33//rbZdy5Yt1V5m36pVK1y6dEntew4AHh4eWpXJlZSUqN3u4OAAa2trPH78WGWbiYlJhX8T6enpauMzNzeHs7Oz2vgq+ptQF5+7u7tSjlmWqakpWrVqpXZfxq7KN1bMmDEDHh4euHfvHqytrXH58mUcPXoUvr6+OHLkiB5CJCIybpaWlsjLy1Mpv3r1Kpo0aSJCREREVNe4ublVeE+0pgRMUxKlKWmU70vTRJq7uztMTU217l+uZcuWAGq24rm8raenp9oY1J5I8PICunaFo78/btrb41aZ+9FvNWqEm/b2SG3SBE0GDy6dde3aVXGpc+PGjTXOvGpKaoHS91xdfBUljRW95w4ODmrjkCeomqiLTyKRaIxBEASNfxOenp4aTwhoOpFQ2YkW+d9EWRYWFmoTeKB0creyvzNjVeWkOy4uDqGhoWjSpAlMTExgYmKC3r17IywsTOU+EiKi+mDEiBEIDQ1VLPghkUiQkpKCuXPn4vXXXxc5OiIiqgukUqnGhTlNTU01bvP09FSbEFWUNGpKlipKGitKUBs1aqRYS0C+MNfZ98/i7PtnMdd+LgbdHIT+N/or6ve/0R8Drw/Ef179j6LetWnXFKule3p6qk1QKzqRUFGC6u7urvY9qmh/gOYTDZoS1IqSRk3lFV2RUFGCKpVK1V6RAJTOqqs7KSAIQoXxaZp91hRfkyZNNC565ujoqPHvpXXr1hpPMNWbpLu4uFhxyYajoyPu3r0LoPSP9erVq7qNjoioFli5ciXu37+Ppk2b4smTJ+jbty9at24NGxsbfPHFF2KHR0REdYSmZKR58+ZqV6kGSpOUqlyyDAAtWrRQm6AWFxdrTEI1JajqkkYvBy90demKri5d0bdNX9g/sUejp40U2xs9bQTHQkcM7jRYUa/s48nUzZACmi+zB0ovgXZxcVEpr+ySZU0Jqr29vcZ75lu2bKk2QQU0n9Bo0qSJyuJqQOn7V1GiqelvokWLFhoT15YtW2qcVdfUl6b3vKITCRX9TVT0nmtK8Cs6kWDsqpx0e3t748KFCwCA7t27Y8WKFThx4gRCQ0N1eq8GEVFtYWtri+PHj2PXrl1Yvnw5pk2bhr179yImJqbSBUGIiIi0pS4ZMTU1hVcFz4nWlBDZ2tqqffQXoDlBrWh/gPoEVZvZYnUJlpubm8YTCZoSVEBzcgioT1ArOpEgj698glpZ0qgpBjMzM41XJGhKris6kQA8u5e+rIpmn+Vt1KnoRIKlpeWze97L0fXfhLpjAjRfkVAbVHkhtfnz5+PRo0cASpeGf+WVV9CnTx84ODhgx44dOg+QiMiYFRUVwdLSEomJiRgwYAAGDBggdkhERFRHqVusrLL7XB0dHWFlZYWnT58qyipLGoHSBPXevXtKZSYmJnBzc6swvvIJakWXsQOliVR5lSWN8gT1ypUrSuV2dnYaTyTI4zt+/LhKeUXxabovuqKkUZ6gZmRkKJW7ubmpnTWXa926Na79s9p3WRWdSFC3WFllfxNNmzZVWQG+osvYy8ZXflX7ik4kyOOr6t+EuuOt7G/C2FX5VMHgwYPx2muvASj9w01KSkJmZiYyMjL4j00iqnfMzMzg7u7OJzcQEZHeabrsu6IERl2CqE3SrW62saLL2CuKo6L4LC0tVe5Lrmz2GVBdrEybY1I3q17ZiQQnJyeV+5Irm32Wx1d2VlabpFFdglrZiQRNCXlF8ZmYmKh8JpWdSJDvs/z716JFiwpPJFS0WJ8m1tbWcHR0VCqrzYuoAdVIutVp3LhxhSvnERHVZfPnz0dISAiys7PFDoWIiOowdZf4SiQStGjRosJ25S/xrWymEVBNULVJGps2baqSoDZs2BD29vYVtlN32bc28ZVNULVJGtUlqJWdSJBIJGrbVRZf+QRVm6RRXSJc2YkEa2trlSelmJqaonnz5hW2M9TfhLOzM8zNzZXK7OzsYGdnV2G71q1bq+SXtTnp1vry8vfee0+reps2bap2MEREtdG3336Lv/76C66urnB3d1e5j/vcuXMiRUZERHWNl5cX7t+/r3jt4uKiktSUp+6yb20f8SWnzeyzugS1VatWlU7OeXp6IjY2VvG6stlnQDV+bWafrayslBJUbS9Zbt26Nf766y/Faxsbm0pPJKibVa8svvIJqjYnEuTxlT3x36xZswpPJMjjK/83UVlf5d9zbU4kmJiYKJ0U0uYydnksZ86cUbzW5kSCMdM66d6yZQvc3d3x/PPPa1yNj4ioPho5cqTYIRARUT3h4eGBEydOKF5XNhMqb1OWtbU1HBwcKmxTPkEFKr63uGxft2/fBlD5yuBl25TNL9TNjpanro428Xl6eiI/Px+AdicS5PGVTVC1mXEtX0ebEwkmJiZK97hrcyJB3tfp06cVr7VpU76OjY1NhZexA0CDBg3QuHFjlb61ie/OnTsAtD+RUP6xa9qcSDBmWkceGBiI7du348aNG3jvvfcwfvx4lTediKg+WrhwodghEBFRPVE+QdUm0XRyclJKUFu2bKnVraGtWrVCXl4egNJkSd2iZ+W1bNlSkXRrex+uutnxypRPULU5kSDv6+LFi4rX1UmgtWnToEEDpXhcXFwglUq16uvvv/+uUl/lE1Rt/iZcXV2VklhtL9329PTE48ePAWh3IkG+b3nSrc1l7IDqMdT2p2RpfU93eHg40tLSMGfOHPzxxx9wc3PDW2+99f/t3X1Q1NX+B/D3IrurFGwiAZIJ6A8hFB/wASGf8ipiUk3WmFqYZqbDmA/VmIwzglko1Zh1qWuRirdMr/lwx+6duHIrmQxQVFACLqKSZooY4UKZPMT5/UG77iPsfneXXZb3a4Zp+e4533POd/G0n+8533Pwn//8hyPfRERERERdwDAYsSTA8vDw0EtnaQCjmycgIABKpbLTPIaBuZQA1ZI2AfrtCA0NtehGgu65Lb2REBQUpBcwS6mfJTcSDM9tamS5szyAZdfccNq3pUG3bjrD62KO4TW25O/PcEs7S6+5q7JqITWlUol58+YhNzcX5eXlGDp0KJKTkxEcHIxff/3VUXUkIiIiIuoRamtr8a9//Qvr16/HzJkz4efnB5lMBplMhoULFxoFqJ0toqahGyxZGsDoprM0aNTd39vUFHVzdM8vpX5SbiRYMo0dMA5QLa2frdfc0hkJhgGqpc8+614zR/5NBAUFaV9bsrCeqfp150XUABtWL9f84xdC6E1ncJT6+nokJSVpV7tLSkrCzZs3O8wjhEBaWhqCgoLQp08fTJkyBWVlZdr3f/nlF7z44osIDw+Hl5cXBg4ciBUrVkCtVju4NUREtmO/SETkfgICAvDII49g48aNyMnJQV1dnVEa3WDHcLVwcwyDOUfl0V2F3NKg0fD8lt5I0B1BtWTEGmgP+jSsmbKsG/RZMo3dsE6WXj/dANWa+ummteRGgmGdLK2flGuuO409ODjY4r8J3WvenRdRA6wMupuamrBnzx5Mnz4d4eHhKC0tRWZmJi5fvqz3B+wI8+fPR0lJCXJycpCTk4OSkhIkJSV1mOfNN9/Eli1bkJmZiaKiIgQGBmL69OnaxROuXr2Kq1ev4u2330ZpaSmys7ORk5ODxYsXO7QtRET2wH6RiMi93X///YiPjzc6LmWqrW6AZOnos+5uHFLKtGZ0Urd+lt5I0A1QpdTPmjyG09ItodsmS559BqC3jZelQS0gbSRY9/yW3kjQ3epLSpnW3EjQrZ+lNxJclcULqSUnJ2Pv3r0YOHAgFi1ahL1791r84diqoqICOTk5KCwsRExMDAAgKysLsbGxqKysRHh4uFEeIQS2bt2KdevWYfbs2QCAXbt2ISAgAJ999hmWLl2KYcOG4cCBA9o8gwcPxhtvvIFnnnkGra2t3XqFPCJyb+wXiYjc0/r16zF27FiMHTsWAQEB+OGHH4yCm5CQEHz//fdWnVc3QDXcE9sSlo4+65Ia1FpKN0D19/e3Or+19dPd1swSugGqpTcSDMu0Jm1FRYVV59cdPbb0RoK5/JZy9N+Eq7L429O2bdswcOBAhIaGIi8vD3l5eSbTHTx40G6V0ygoKIBKpdJ+sQSA8ePHQ6VSIT8/3+SXy+rqatTU1OjdHVQqlZg8eTLy8/OxdOlSk2Wp1Wr4+Ph0+MWyqakJTU1N2t81qzoSUc/0xx9/IDs7G1999RVqa2uNHrn5+uuv7V4m+0UiIve0YcOGTtNIWclZN0CVwsvLy+o8pv5fZI6Pj4/V59clJWi0Jqi77777rD6/ray5kWDJ3teGbL2RLuVGwpAhQyxO29kWZt2JxVd6wYIFkv6Y7aGmpsbkH52/vz9qamrM5gHan4vRFRAQoN3GwFBdXR02btxo9ounxqZNmyzqEImoZ1i5ciWys7Mxa9YsDBs2rEv6SvaLREQ9l+60b1fm6tsLWxM0OmOmlzXfJxz9qK+9WBNIOyv2dASL/3qys7PtXnhaWlqnX9KKiooAmL7oQohOPwzD983laWhowKxZsxAZGdnpnrspKSl46aWX9PJa+pwGEbmfvXv3Yt++fXj44YdtPhf7RSIiIiL34tSH85YvX465c+d2mCYkJARnz57F9evXjd67ceOG0YiNRmBgIID2kR3drQtqa2uN8jQ2NiIhIQF33303Dh061Ol+c0ql0qJ9ComoZ1AoFJKmdZnCfpGIiIjIvTg16Pbz84Ofn1+n6WJjY6FWq3HixAmMGzcOAHD8+HGo1WrExcWZzBMaGorAwEDk5uZi1KhRAIDm5mbk5eUhIyNDm66hoQEzZsyAUqnE4cOHJT2bQEQ928svv4x3330XmZmZNk+FYr9IRERE5F66xTK0DzzwABISErBkyRJ8+OGHAIAXXngBiYmJegs0REREYNOmTXj88cchk8mwatUqpKenIywsDGFhYUhPT4eXlxfmz58PoH0kJz4+Hrdu3cKnn36KhoYG7eI/9957r80LThBRz3Ds2DF88803+PLLLzF06FCjUWFHLDDJfpGIiIioe+gWQTcA7N69GytWrNCuuvvoo48iMzNTL01lZSXUarX29zVr1uD3339HcnIy6uvrERMTgyNHjsDb2xsAcOrUKRw/fhyA8Yp/1dXVbrVMPRE5zj333IPHH3+8y8tlv0hERETk+rpN0O3r64tPP/20wzRCCL3fZTIZ0tLSkJaWZjL9lClTjPIQEVlr586dTimX/SIRERGR6/NwdgWIiIiIiIiI3FW3GekmInJl+/fvx759+3D58mU0NzfrvXf69Gkn1YqIiIiInI0j3URENnrvvfewaNEi+Pv7o7i4GOPGjUO/fv1w8eJFzJw509nVIyIiIiInYtBNRGSjDz74AB999BEyMzOhUCiwZs0a5ObmYsWKFXqLmBERERFRz8Ogm4jIRpcvX9bujd2nTx80NjYCAJKSkrBnzx5nVo2IiIiInIxBNxGRjQIDA1FXVwcACA4ORmFhIYD2Lba4EjgRERFRz8aF1IiIbDR16lR88cUXiI6OxuLFi7F69Wrs378fJ0+exOzZs51dPSIi6kaOHTuG8+fPa3//+eefta/Pnz+P7OxstLa2ori42BnVIyIJGHQTEdnoo48+QltbGwBg2bJl8PX1xbFjx/DII49g2bJlTq4dERF1Jx9//DF27dpl8r3vvvsO3333XRfXiIhsxaCbiMhGHh4e8PC487TOnDlzMGfOHCfWiIiIiIhcBZ/pJiKyg2+//RbPPPMMYmNj8dNPPwEAPvnkExw7dszJNSMiou4kOzsbQogOf5qampCWloa0tDRnV5eILMCgm4jIRgcOHMCMGTPQp08fFBcXo6mpCQDQ2NiI9PR0J9eOiIiIiJyJQTcRkY1ef/11bNu2DVlZWZDL5drjcXFxOH36tBNrRkRERETOxqCbiMhGlZWVmDRpktFxHx8f3Lx5s+srREREREQug0E3EZGN+vfvr7e9i8axY8cwaNAgJ9SIiIiIiFwFg24iIhstXboUK1euxPHjxyGTyXD16lXs3r0br7zyCpKTk51dPSIiclFCCDQ3N6O5uRlCCKvymXrdXfN0ZVmsX/fIY0s+V8Qtw4iIbLRmzRqo1Wo89NBDuH37NiZNmgSlUolXXnkFy5cvd3b1iIjIRbW0tGDTpk0AgJSUFCgUCovz6b5WKpXdKk9VXRUamxv1jt2+dVv7+tSVU+jt1VvvfW+FN8L6hXVJ/eyVj/WTnseWfK6IQTcRkR288cYbWLduHcrLy9HW1obIyEjcfffdzq4WERGR5aqqgEadYPj2nUAYZ88CvXsb5/H2BsLCjI+bK6KuCkMyhxgd94IX1mANAGD6p9NxC7eM0pxbfs4o8CbqDhh0ExHZiZeXF8aMGePsahARkZvT3SlD97VNeaqqgCH6wbBcLgfWrWt/PWUKoDPyqOfcOW3g3Vk5hiPcGi1oMfm6o7wOuQ52zGdRHoMbHXKdaywvKwMM85m5ydFV18LVr7mrYtBNRCTRc889Z1G6HTt2OLgmRETUk8hkMpOvbcrTaBwMy8y87iivlLpJ5ZDr0JVlmbjRIdO50SGbONH0jQ6dmxzW1M9wWn9rS6v29ZnrZ+Ap1w8NTU3pd/Vr7qoYdBMRSZSdnY3g4GCMGjWq2y/wQURERF3MxI0OR+UzNa1fDjnWoT3An7BzgskZBpzSbx8MuomIJFq2bBn27t2Lixcv4rnnnsMzzzwDX19fZ1eLiIhcmO5oo+5IY0lNid5Io9Eoo+405NY7+VBSAnj+mc/K56up5zA3rd9R+Ugfg24iIok++OADvPPOOzh48CB27NiBlJQUzJo1C4sXL0Z8fHy3nwpFRET2ZTja2NlIo3aU0XAass4UZEyYoD8F2cTUYyJyLu7TTURkA6VSiXnz5iE3Nxfl5eUYOnQokpOTERwcjF9//dXZ1SMiIhdi7aihNr0104mlTlkmIodh0E1EZCcymQwymQxCCLS1tTm7OkRERETkAhh0ExHZoKmpCXv27MH06dMRHh6O0tJSZGZm4vLly9ynm4iIyJVUVbU/A69RUgKcPt3+U1XlrFqRBJcuXUJTU5Pdz1tfX4+1a9ciIiICffr0gb+/P6ZNm4bPP/8cQPsiuppBlh9++MHi83aboLu+vh5JSUlQqVRQqVRISkrCzZs3O8wjhEBaWhqCgoLQp08fTJkyBWVlZWbTzpw5EzKZDP/85z/t3wAicjvJycno378/MjIykJiYiCtXruDzzz/Hww8/DA8Px3ev7BeJiIgspHkufsKEO8cmTABGj27/GTKEgbcDVNVVoaSmRPt7SU0JTl87jdPXTqOqTvr13rBhAz766CM71PAOzWOCGRkZqKysxO3bt3Hjxg189dVXmDNnDhYvXiz53N1mIbX58+fjypUryMnJAQC88MILSEpKwhdffGE2z5tvvoktW7YgOzsbQ4YMweuvv47p06ejsrIS3t7eemm3bt3KRY+IyCrbtm3DwIEDERoairy8POTl5ZlMd/DgQYeUz36RiIjIQpY8624uTVUVUF9/53fNivFcLb5DmoUDO1owUOqWZM3Nzfj2228xY8YMDDHY61wKtVqNGTNm4Nq1awCAp556Cs8++yz8/f1x7tw5bNmyBTt27EBpaamk83eLoLuiogI5OTkoLCxETEwMACArKwuxsbGorKxEeHi4UR4hBLZu3Yp169Zh9uzZAIBdu3YhICAAn332GZYuXapNe+bMGWzZsgVFRUXo379/p/VpamrSm87Q0NBgaxOJqBtasGCB04JS9otERERdQDNCbm7F+J62WvyFC3ded3LzwZKFA23dkiwrKwsZGRk2zzB87bXXcOXKFQBAeno6UlJStO+NHj0aTz75JBITE3HkyBFJ5+8WQXdBQQFUKpX2iyUAjB8/HiqVCvn5+Sa/XFZXV6Ompgbx8fHaY0qlEpMnT0Z+fr72y+WtW7cwb948ZGZmIjAw0KL6bNq0CRs2bLCxVUTU3WVnZzutbPaLREREXaCzEfKetFp8VRUwYoRL3Xy4dOkSjh49iqlTp0o+R1NTE3bu3AkAGD58OF599VWjNHK5HNu3b8egQYPQ0tJi9H5nusUz3TU1NfD39zc67u/vj5qaGrN5ACAgIEDveEBAgF6e1atXIy4uDo899pjF9UlJSYFardb+/PjjjxbnJSKyB/aLRERE1KU6usHggJsPF365M6peUlNi9hnwTz75BL/99pvkck6dOoX6Px8fePbZZ82Omg8YMEBv4MIaTg2609LStKu/mfs5efIkAJicwimE6HRqp+H7unkOHz6Mr7/+Glu3brWq3kqlEj4+Pno/RET2wH6RiIjcRUeBElFHquqqMOLDEdrfJ+ycgCGZQ0z+Pd26dQv79++XXJbuc9pjx47tMO24ceMkleHU6eXLly/H3LlzO0wTEhKCs2fP4vr160bv3bhxw2jERkMzJbKmpkbvecTa2lptnq+//hoXLlzAPffco5f3iSeewMSJE3H06FErWkNEZDv2i0RE1J3pjk5qFs2SulgWWUH3WesLF4AHHnBeXezA3LPepo4LIfDll1/iL3/5CwYMGGB1WfU6i+SZmkWoy9x3rM44Nej28/ODn59fp+liY2OhVqtx4sQJ7d2F48ePQ61WIy4uzmSe0NBQBAYGIjc3F6NGjQLQvspdXl4eMjIyAABr167F888/r5cvKioK77zzDh555BFbmkZEJAn7RSIi6s5MBUW2LpblFhwZCBs+az1iBFBW1rMWeAOwfft2rF+/3upFboUQ2ted5dVNa41u8Uz3Aw88gISEBCxZsgSFhYUoLCzEkiVLkJiYqLdYUEREBA4dOgSg/YKtWrUK6enpOHToEL7//nssXLgQXl5emD9/PoD2UZ9hw4bp/QDQbgFEROSq2C8SERHp0B3pdRW6dRoxwnH7gJt6nronLfAGoK2tDWVlZSgqKrI6r6+vr/a1qVmEumpra60+P9BNVi8HgN27d2PFihXah9cfffRRZGZm6qWprKyEWq3W/r5mzRr8/vvvSE5ORn19PWJiYnDkyBGjvWiJiLoj9otERN1La0sr5JBrfzf3Wjd9c3Mz0NravmXVn5rNvG7P1Ao0Nxudq7myUv+14S4XBmV0Wo6ZMltbWo3apG0HjK+BYVrD13rF6JzHqE1jxgAnTwKDB5uvo1xu/bXrLJ+Z6w0AzTrTlpvl8va9vnXTSr3mhmW2thrnM0hj6rpbfc1NlWOuTjpldlSO4Wdqrr6Gf0uadW50yWQyZGdnIzIyEgqFwmR7tOeTy7X5o6KitMeLioowceJEs/mkBPUAIBNSx8hJq6GhASqVCmq1mosHEbkA/pt0Pn4GRK6F/yZdA7dWJHINKSkp2sC8qakJ/fv3R319PUaOHInTp0+bnGb+008/YdCgQdqbBNXV1QgJCbGovG4xvZyIiIiIiIjI3pRKJRYtWgQAKCkpwVtvvWWUprW1FUuWLDE5Km+JbjO9nIiIiIioO5u5aCYm7Jygd0wzbbYFLUbpjy06hpGBI4GSEmDCnXwCQMufU3vlLS3QG5M7dgwYOdLoXOL8ebT8uR2SvKgIsv/7P/0EBmV0Wo6ZMouvFWNq9lS9NmnbgfZtxAyvgUZH18LwPAAgiovRMnXqnfqZabtu+6y+dp3l66DMTusn9ZqbOI+YMEE/n0Eac9fdqmtuqhxzdTIo01w5hp+pYV7DfJr0ixcvNhkAL1iwANOnTzfZFl1yg6n769evx759+3DlyhW8+uqrKCkpwYIFC+Dv749z585hy5YtKCoqwtixYyVNMWfQTURERETUBTzlnkZBh7lgR5NeoVAAnp5Ai346ZYuZfJ6egKnnWSMjoSwpaX9talVrE2V0WI6ZMuUKOW7hlv7bmnbA9DXQ6OhaGJ4HAODrC+UtnbL69jXddk0d/2yLVdeus3zmrrcl9ZN6zQ3L/PM8evkM0pi77lZdc1PlmKuTQZnmyjH6TA3yGubTpBdC6K0k7uHhgcDAQCQkJKBXr14dtskUlUqFnJwcTJs2DTU1NdizZw/27Nmjl2bRokWYNGmSdlTcGpxeTkRERETUE4SFudc2UmFhwLlzwKlT7f91tbZ1Vf1MLYbawxZIbWtrw/PPPy8p4NYYOnQoysrKsGbNGoSFhUGpVMLPzw8PPfQQPvvsM+zYsUPyuTnSTURERERE3ZOrBdqGuqJ+muBes02Yt7frXxc78vDwwNixY7XbnNrC19cXGRkZyMjIsEPN7mDQTUREREREduGtMB5hNXWM7KwHBdmGPDw8sGDBAmdXo0MMuomIiIiIyC7C+oXh3PJzaGxuH3X1VngjrF/PDQhJGnM3agyPy2QyPP7447j33nu7olqSMegmIiIiIiK7YZDtpjp6TtzOz5Ab3rwBTN/A6du3Lx577DG7lu0IDLqJiIiIiIhM6SyY7EkLlhk+O67hoGfILbl5s3DhQpOrn7saBt1ERERERESmmAs0gR63YBkAq9prybP8Up/39/DwQGRkJGJiYiTl72oMuomIiIiIyL1ZMiJtLk1PC6ztxNQUcV22PO+/bNkyDBkyBDKZzJYqWm3hwoVYuHCh1fkYdBMRERERkXvraMQa6Jmj1l3AUc/3x8XFOeS8jsKgm4iIiIiI3B+DanISD2dXgIiIiIiIiMhdMegmIiIiIuoC1i4apU1vzQrZPWk1baJugtPLiYiIiIi6QGcLS+nSW2Sqs+eRtZn4XDKRK2LQTURERETURSQvLMVgmmwgdWsuqflIH4NuIiIiIiKirib1UQAJ+ayZZaEtxoYtvUgfg247EEIAABoaGpxcEyIC7vxb1PzbpK7HfpHItbBfpE7Z8iw4nyOXxtLHBnTZ8AgBA2jnkQn2vja7cuUK7r//fmdXg4gM/PjjjxgwYICzq9EjsV8kck3sF4mIuh6Dbjtoa2vD1atX4e3tDZlMZjZdQ0MD7r//fvz444/w8fHpwho6hru1B3C/NvXU9ggh0NjYiKCgIHh4cJMGZ2C/yPa4KndrE/tFIiLXx+nlduDh4WHVXWMfHx+3+B+9hru1B3C/NvXE9qhUqi6qDZnCfpHtcXXu1ib2i0RErou3OomIiIiIiIgchEE3ERERERERkYMw6O5CSqUSqampUCqVzq6KXbhbewD3axPbQ67O3T5Ttsf1uVub3K09RETuiAupERERERERETkIR7qJiIiIiIiIHIRBNxEREREREZGDMOgmIiIiIiIichAG3UREREREREQOwqCbiIiIiIiIyEEYdNugvr4eSUlJUKlUUKlUSEpKws2bNzvMI4RAWloagoKC0KdPH0yZMgVlZWV6aaZMmQKZTKb3M3fuXJvLdkZ7fvnlF7z44osIDw+Hl5cXBg4ciBUrVkCtVuudJyQkxKjNa9eutboNH3zwAUJDQ9G7d2+MHj0a3377bYfp8/LyMHr0aPTu3RuDBg3Ctm3bjNIcOHAAkZGRUCqViIyMxKFDh2wu11ntycrKwsSJE9G3b1/07dsX06ZNw4kTJ/TSpKWlGX0WgYGBLtme7Oxso7rKZDLcvn3bpnJJOvaLrtUvuluf6Ig2sV9kv0hE5HCCJEtISBDDhg0T+fn5Ij8/XwwbNkwkJiZ2mGfz5s3C29tbHDhwQJSWloqnnnpK9O/fXzQ0NGjTTJ48WSxZskRcu3ZN+3Pz5k2by3ZGe0pLS8Xs2bPF4cOHxfnz58VXX30lwsLCxBNPPKF3nuDgYPHaa6/ptbmxsdGq+u/du1fI5XKRlZUlysvLxcqVK8Vdd90lLl26ZDL9xYsXhZeXl1i5cqUoLy8XWVlZQi6Xi/3792vT5Ofni169eon09HRRUVEh0tPThaenpygsLJRcrjPbM3/+fPH++++L4uJiUVFRIRYtWiRUKpW4cuWKNk1qaqoYOnSo3mdRW1trU1sc1Z6dO3cKHx8fvbpeu3bNpnLJNuwXXadfdLc+0VFtYr/IfpGIyNEYdEtUXl4uAOh90SgoKBAAxP/+9z+Tedra2kRgYKDYvHmz9tjt27eFSqUS27Zt0x6bPHmyWLlypV3LdmZ7DO3bt08oFArR0tKiPRYcHCzeeecdSXXXGDdunFi2bJnesYiICLF27VqT6desWSMiIiL0ji1dulSMHz9e+/ucOXNEQkKCXpoZM2aIuXPnSi7XUo5oj6HW1lbh7e0tdu3apT2WmpoqRowYIb3iZjiiPTt37hQqlcqu5ZJ07Bddq190tz5RyrnZL9qnXCIisg2nl0tUUFAAlUqFmJgY7bHx48dDpVIhPz/fZJ7q6mrU1NQgPj5ee0ypVGLy5MlGeXbv3g0/Pz8MHToUr7zyChobG20q29nt0aVWq+Hj4wNPT0+94xkZGejXrx9GjhyJN954A83NzRbXv7m5GadOndKrCwDEx8ebrUtBQYFR+hkzZuDkyZNoaWnpMI3mnFLKdWZ7DN26dQstLS3w9fXVO15VVYWgoCCEhoZi7ty5uHjxouS2AI5tz6+//org4GAMGDAAiYmJKC4utqlcko79ouv0i+7WJzqyTYbYLxIRkb15dp6ETKmpqYG/v7/RcX9/f9TU1JjNAwABAQF6xwMCAnDp0iXt708//TRCQ0MRGBiI77//HikpKThz5gxyc3Mll+3M9uiqq6vDxo0bsXTpUr3jK1euRHR0NPr27YsTJ04gJSUF1dXV+Pjjjy2q/88//4w//vjDZF06qr+p9K2trfj555/Rv39/s2k055RSrjPbY2jt2rW47777MG3aNO2xmJgY/P3vf8eQIUNw/fp1vP7664iLi0NZWRn69evnUu2JiIhAdnY2oqKi0NDQgHfffRcPPvggzpw5g7CwMId9PmQa+0XX6RfdrU90ZJsMsV8kIiJ7Y9BtIC0tDRs2bOgwTVFREQBAJpMZvSeEMHlcl+H7hnmWLFmifT1s2DCEhYVhzJgxOH36NKKjo60q2xXao9HQ0IBZs2YhMjISqampeu+tXr1a+3r48OHo27cvnnzySe0oj6UsrUtH6Q2PW3JOa8u1lCPao/Hmm29iz549OHr0KHr37q09PnPmTO3rqKgoxMbGYvDgwdi1axdeeuklSe3oqH62tGf8+PEYP3689v0HH3wQ0dHR+Otf/4r33ntPcrmkzxX6EfaL0vpFd+sTpZyb/SL7RSIiZ2PQbWD58uVGK+IaCgkJwdmzZ3H9+nWj927cuGF091hDs9JpTU2N3t312tpas3kAIDo6GnK5HFVVVYiOjkZgYKDFZbtKexobG5GQkIC7774bhw4dglwu77BOmi8M58+ft+jLpZ+fH3r16mV0l76jaxsYGGgyvaenp7ZMc2k055RSriUc1R6Nt99+G+np6fjvf/+L4cOHd1iXu+66C1FRUaiqqpLQknaObo+Gh4cHxo4dq62roz6fnsZV+hFd7Bc77hfdrU90ZJs02C+yXyQichQ+023Az88PERERHf707t0bsbGxUKvVetuKHD9+HGq1GnFxcSbPrZkaqZkOCbQ/W5WXl2c2DwCUlZWhpaVF+wXOmrJdoT0NDQ2Ij4+HQqHA4cOH9UYPzNE8f2Zq6p8pCoUCo0eP1qsLAOTm5pqtf2xsrFH6I0eOYMyYMdovv+bSaM4ppVxntgcA3nrrLWzcuBE5OTkYM2ZMp3VpampCRUWFxZ+FKY5sjy4hBEpKSrR1ddTn09O4Qj9iiP1ix9ytT3RkmwD2i52VS0RENuqK1drcVUJCghg+fLgoKCgQBQUFIioqymgrmfDwcHHw4EHt75s3bxYqlUocPHhQlJaWinnz5ultJXP+/HmxYcMGUVRUJKqrq8W///1vERERIUaNGiVaW1utKtsV2tPQ0CBiYmJEVFSUOH/+vN72JZr25Ofniy1btoji4mJx8eJF8Y9//EMEBQWJRx991Kr6a7ZA2b59uygvLxerVq0Sd911l/jhhx+EEEKsXbtWJCUladNrtl5ZvXq1KC8vF9u3bzfaeuW7774TvXr1Eps3bxYVFRVi8+bNZrfHMVeuVI5oT0ZGhlAoFGL//v1mtyF6+eWXxdGjR8XFixdFYWGhSExMFN7e3i7ZnrS0NJGTkyMuXLggiouLxaJFi4Snp6c4fvy4xeWSfbFfdJ1+0d36REe1if0i+0UiIkdj0G2Duro68fTTTwtvb2/h7e0tnn76aVFfX6+XBoDYuXOn9ve2tjaRmpoqAgMDhVKpFJMmTRKlpaXa9y9fviwmTZokfH19hUKhEIMHDxYrVqwQdXV1VpftCu355ptvBACTP9XV1UIIIU6dOiViYmKESqUSvXv3FuHh4SI1NVX89ttvVrfh/fffF8HBwUKhUIjo6GiRl5enfe/ZZ58VkydP1kt/9OhRMWrUKKFQKERISIj429/+ZnTOzz//XISHhwu5XC4iIiLEgQMHrCrXFvZuT3BwsMnPIjU1VZtGs6ewXC4XQUFBYvbs2aKsrMwl27Nq1SoxcOBAoVAoxL333ivi4+NFfn6+VeWSfbFfdK1+0d36REe0if0i+0UiIkeTCfHnChxEREREREREZFd8ppuIiIiIiIjIQRh0ExERERERETkIg24iIiIiIiIiB2HQTUREREREROQgDLqJiIiIiIiIHIRBNxEREREREZGDMOgmIiIiIiIichAG3UREREREREQOwqCbiIiIiIiIyEEYdBMRERERERE5CINuIiIiIiIiIgf5f+WPeQ6kj7UqAAAAAElFTkSuQmCC",
+ "text/plain": [
+ "<Figure size 1000x1000 with 6 Axes>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "ename": "ValueError",
+ "evalue": "cannot reshape array of size 25 into shape (50,20)",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "Cell \u001b[0;32mIn[16], line 30\u001b[0m\n\u001b[1;32m 28\u001b[0m scoreboard\u001b[38;5;241m.\u001b[39mflush()\n\u001b[1;32m 29\u001b[0m fig\u001b[38;5;241m.\u001b[39mclf()\n\u001b[0;32m---> 30\u001b[0m \u001b[43mplot_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43mq\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstep\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mepsilon_trace\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mr_trace\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 31\u001b[0m scoreboard\u001b[38;5;241m.\u001b[39mall_goals \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m\n\u001b[1;32m 32\u001b[0m clear_output(wait\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n",
+ "Cell \u001b[0;32mIn[15], line 25\u001b[0m, in \u001b[0;36mplot_status\u001b[0;34m(q, step, epsilon_trace, r_trace)\u001b[0m\n\u001b[1;32m 22\u001b[0m binSize \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m20\u001b[39m\n\u001b[1;32m 23\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m step\u001b[38;5;241m+\u001b[39m\u001b[38;5;241m1\u001b[39m \u001b[38;5;241m>\u001b[39m binSize:\n\u001b[1;32m 24\u001b[0m \u001b[38;5;66;03m# Calculate mean of every bin of binSize reinforcement values\u001b[39;00m\n\u001b[0;32m---> 25\u001b[0m smoothed \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mmean(\u001b[43mr_trace\u001b[49m\u001b[43m[\u001b[49m\u001b[43m:\u001b[49m\u001b[38;5;28;43mint\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mstep\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mbinSize\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mbinSize\u001b[49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mreshape\u001b[49m\u001b[43m(\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mint\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mstep\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mbinSize\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbinSize\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m, axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m)\n\u001b[1;32m 26\u001b[0m plt\u001b[38;5;241m.\u001b[39mplot(np\u001b[38;5;241m.\u001b[39marange(\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1\u001b[39m \u001b[38;5;241m+\u001b[39m \u001b[38;5;28mint\u001b[39m(step \u001b[38;5;241m/\u001b[39m binSize)) \u001b[38;5;241m*\u001b[39m binSize, smoothed)\n\u001b[1;32m 27\u001b[0m plt\u001b[38;5;241m.\u001b[39mylabel(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mMean reinforcement\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
+ "\u001b[0;31mValueError\u001b[0m: cannot reshape array of size 25 into shape (50,20)"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "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vMjzoinvn2I8L3KKpGrz7bffCs2bNxdsbGyEbt266Uw5ZEoAlPrYvHmzIAiC8PDhQyEgIEBo0qSJYG1tLXh6egpTpkwR0tLSdNbz6NEjYe7cuUKjRo0EOzs7YcSIESX61ITieWatra0Fd3d34aWXXhIuXryofV2tVguffPKJ4OrqKojFYuG5554Tzp8/r7MOU9mWYn/88Yd2jron1YZ9Uzy1wL8fU6ZMEQTBcPsjOztbeO211wQHBwfBwcFBeO2118qdzoAMp7xz3ZQpU4R+/frp9D98+LDQtWtXwcbGRmjRooWwdu3aEuv86aefhLZt2wrW1tZCu3btdL5sVLfKbE+/fv3K/fctCIIQHBwseHp6CjY2NkKTJk2EgIAAIS4uziS3Z9myZUKrVq0EW1tboWHDhsKzzz4r/Pe//y2xztqyfwRBM82anZ2dsGHDhlLXZ8z987TzY208fkiD50WeF011/wgCz4v67h+RIDy+25iIiIiIiIiojuM9wURERERERGQ2mAQTERERERGR2WASTERERERERGaDSTARERERERGZDSbBREREREREZDaYBBMREREREZHZYBJMREREREREZoNJMBEREREREZkNJsFERERERERkNpgEExERERERkdlgEkxERERERERmg0kwERERERERmQ0mwURERERERGQ2mAQTERERERGR2WASTERERERERGaDSTARERERERGZDSbBREREREREZDaYBBMREREREZHZYBJMREREREREZoNJMBEREREREZkNJsFERERERERkNpgEExERERERkdlgEkxERERERERmg0kwERERERERmQ0mwURERERERGQ2mAQTERERERGR2WASTERERERERGaDSTARERERERGZDSbBREREREREZDaYBBMREREREZHZYBJMREREREREZoNJMBEREREREZkNJsFERERERERkNpgEExERERERkdlgEkxERERERERmw8rYARibWq3GnTt34ODgAJFIZOxwiAiAIAjIy8uDu7s7LCz4W11N43mRyPTwvEhEZDhmnwTfuXMHHh4exg6DiEqRnp6OZs2aGTsMs8PzIpHp4nmRiKjqzD4JdnBwAKD5T8XR0dHI0RARAMjlcnh4eGiPT6pZPC8SmR6eF4mIDMfsk+DiS/0cHR35ZY/IxPBSXOPgeZHIdPG8SERUdbyphIiIiIiI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",
+ "text/plain": [
+ "<Figure size 1000x1000 with 5 Axes>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "old_X, old_q = pick_greedy_action(q, p1, epsilon)\n",
+ "game_engine.player_advance([old_X[1]])\n",
+ "\n",
+ "fig = plt.figure(figsize=(10, 10))\n",
+ "scoreboard = Scoreboard()\n",
+ "plot_spacing = 1000\n",
+ "plotted_steps = 0\n",
+ "\n",
+ "R = np.zeros((plot_spacing, 1))\n",
+ "r_trace = np.zeros(n_steps // plot_spacing)\n",
+ "\n",
+ "for step in range(n_steps):\n",
+ " new_X, new_q = pick_greedy_action(q, p1, epsilon)\n",
+ " outcomes = game_engine.player_advance([new_X[1]])\n",
+ " scoreboard.track_outcome(outcomes[p1])\n",
+ "\n",
+ " update_q(q, old_X, new_X, new_q, outcomes[p1], n_epochs, lr=learning_rate)\n",
+ "\n",
+ " epsilon *= epsilon_decay\n",
+ " epsilon_trace[step] = epsilon\n",
+ " R[step % plot_spacing, 0] = reinforcement(outcomes[p1])\n",
+ " old_X = new_X\n",
+ " old_q = new_q\n",
+ "\n",
+ " if step >= plotted_steps:\n",
+ " r_trace[plotted_steps // plot_spacing] = np.mean(R)\n",
+ " plotted_steps += plot_spacing\n",
+ " scoreboard.flush()\n",
+ " fig.clf()\n",
+ " plot_status(q, step, epsilon_trace, r_trace)\n",
+ " scoreboard.all_goals = 0\n",
+ " clear_output(wait=True)\n",
+ " display(fig)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "269ac824-1568-49aa-a020-9a57ee59ae49",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "game_engine.toggle_draw()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "36a2d897-15a8-47a4-953b-a159af0ad881",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "epsilon = 0\n",
+ "for step in range(500):\n",
+ " new_X, _ = pick_greedy_action(q, p1, epsilon)\n",
+ " game_engine.player_advance([new_X[1]])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b77b2db1-e928-4cd8-ae98-7f8ac9b1326f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "inferior_table = qtsnake.load_q('inferior_qt.npy')\n",
+ "superior_table = qtsnake.load_q('superior_qt.npy')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1022bbdf-c68d-4e02-89e0-9d71470d9b8e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "epsilon = 0\n",
+ "n_steps = 1500"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d67ba96c-9b42-47d2-a88f-a94335bd6967",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "game_engine = multiplayer.Playfield(window_width=WINDOW_WIDTH,\n",
+ " window_height=WINDOW_HEIGHT,\n",
+ " units=10,\n",
+ " g_speed=100,\n",
+ " s_size=1)\n",
+ "t1 = game_engine.add_player()\n",
+ "t2 = game_engine.add_player()\n",
+ "n1 = game_engine.add_player()\n",
+ "game_engine.start_game()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c5be5beb-e92c-42ad-9076-c28394560122",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "q_table = qtsnake.QSnake(game_engine)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "314d0836-5c99-4de3-91c8-e563fed61e6c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for step in range(n_steps):\n",
+ " # table 1\n",
+ " _, t1_action = q_table.pick_greedy_action(inferior_table, t1, epsilon)\n",
+ "\n",
+ " # table 2\n",
+ " _, t2_action = q_table.pick_greedy_action(superior_table, t2, epsilon)\n",
+ "\n",
+ " # network 1\n",
+ " n1_state_action, _ = pick_greedy_action(q, n1, epsilon)\n",
+ " game_engine.player_advance([t1_action,\n",
+ " t2_action,\n",
+ " n1_state_action[1]])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2c75448e-3216-48f1-b649-938711cd4870",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/revised_snake_q_table.ipynb b/revised_snake_q_table.ipynb
new file mode 100644
index 0000000..3d41eaf
--- /dev/null
+++ b/revised_snake_q_table.ipynb
@@ -0,0 +1,743 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "85da5df2-c926-417c-bd7b-d214ad31ebe1",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "pygame 2.5.1 (SDL 2.28.2, Python 3.11.5)\n",
+ "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
+ ]
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "from collections import namedtuple\n",
+ "from IPython.core.debugger import Pdb\n",
+ "\n",
+ "from GameEngine import multiplayer\n",
+ "Point = namedtuple('Point', 'x, y')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d264ae4a-380c-47b6-9b29-c6fe16c6399c",
+ "metadata": {},
+ "source": [
+ "### New Game Implementation\n",
+ "\n",
+ "I have an improved game implementation which allows for multiplayer snake games, as well as simplified training. This notebook will go over both of these, including an implementation of q-table learning, as well as a match between a manually filled out q-table and a learned one.\n",
+ "\n",
+ "Let's start by initializing the engine object:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "2a382240-906d-474f-94c0-9af1a5de97ae",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# defines game window size and block size, in pixels\n",
+ "WINDOW_WIDTH = 640\n",
+ "WINDOW_HEIGHT = 480\n",
+ "GAME_UNITS = 80"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "976eff80-c50a-492e-a49b-975d9905e274",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "game_engine = multiplayer.Playfield(window_width=WINDOW_WIDTH,\n",
+ " window_height=WINDOW_HEIGHT,\n",
+ " units=GAME_UNITS,\n",
+ " g_speed=35,\n",
+ " s_size=1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23de2fca-d31a-497f-9a0c-845c568e5df7",
+ "metadata": {},
+ "source": [
+ "Here is a run-down of the current functions available to programs utilizing the game engine:\n",
+ "\n",
+ "**add_player**: Returns the player's number to the callee (between 0-3, for a total of 4 players). This number can be used with other functions to index that player's state.\n",
+ "\n",
+ "**get_heads_tails_and_goal**: Returns an array of player heads (in order of player number) the locations of all snake tails, as well as the goal location. Each is stored in an array of named tuples.\n",
+ "\n",
+ "**get_viable_actions**: Given a player's id, returns a list of integers corresponding to actions which will not immediately result in the snake's death.\n",
+ "0 = UP\n",
+ "1 = RIGHT\n",
+ "2 = DOWN\n",
+ "3 = LEFT\n",
+ "\n",
+ "**start_game**: Initializes goal, player, score, and playfield objects. Disables the ability to add new players. Enables use of player_advance function.\n",
+ "\n",
+ "**stop_game**: Sets the game_state to false, allowing new players to be added.\n",
+ "\n",
+ "**cleanup**: Quits the pygame window.\n",
+ "\n",
+ "**player_advance**: Given an array corresponding to each player's action (integers), returns a list of collision results, and updates the internal game state.\n",
+ "\n",
+ "**toggle_draw**: Turns off/on the game UI for faster training.\n",
+ "\n",
+ "Let's do a test of these functions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "64470c1a-ddc6-4ce1-b6d4-f1e17e3d0176",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Game starting with 1 players.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "p1 = game_engine.add_player()\n",
+ "game_engine.start_game()\n",
+ "p1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "28d7fa43-4419-4940-9d4f-d6bd5ccc11ec",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "viable_actions = game_engine.get_viable_actions(p1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "ba45b03a-9c42-48e9-a259-5d01e667157d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[<CollisionType.NONE: 2>]"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "game_engine.player_advance([np.random.choice(viable_actions)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "df7170ae-e95d-404b-b632-e8e9de46d69e",
+ "metadata": {},
+ "source": [
+ "If you looked at the UI for this last statement, you should have seen that the game moved the snake (yellow) in a random direction away from immediate death."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "144bc5ff-756c-4e07-a855-4020a4474d52",
+ "metadata": {},
+ "source": [
+ "### State-sensing methods, creating and reading a q-table\n",
+ "Now, we can start redesigning some functions used to allow the snake to play intelligently. We'll use a multi-dimensional numpy array to store the rewards corresponding to each state and action. This is called a q-function, or a q-table in this case.\n",
+ "\n",
+ "How many states do I need? Seeing how the new **get_viable_actions** method already prevents the snake from choosing life-ending moves, the snake is no longer tasked with learning or memorizing it.\n",
+ "\n",
+ "The snake doesneed to be able to interpret progress towards the goal, so I will reinclude one state-sensing to sense the goal direction. I need only 8 states (with entries for each four actions) to represent our game now."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "88a69876-fbc7-4dc1-a471-d8449fada4e4",
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([[0., 0., 0., 0.],\n",
+ " [0., 0., 0., 0.],\n",
+ " [0., 0., 0., 0.],\n",
+ " [0., 0., 0., 0.],\n",
+ " [0., 0., 0., 0.],\n",
+ " [0., 0., 0., 0.],\n",
+ " [0., 0., 0., 0.],\n",
+ " [0., 0., 0., 0.]])"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "goal_relations = 8\n",
+ "actions = 4\n",
+ "q = np.zeros((goal_relations,\n",
+ " actions))\n",
+ "q"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "3e15744e-1251-4315-8a4c-ed5788d04478",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def sense_goal(head, goal):\n",
+ " '''\n",
+ " maps head and goal location onto an\n",
+ " integer corresponding to approx location\n",
+ " '''\n",
+ " diffs = Point(goal.x - head.x, goal.y - head.y)\n",
+ "\n",
+ " if diffs.x == 0 and diffs.y < 0:\n",
+ " return 0\n",
+ " if diffs.x > 0 and diffs.y < 0:\n",
+ " return 1\n",
+ " if diffs.x > 0 and diffs.y == 0:\n",
+ " return 2\n",
+ " if diffs.x > 0 and diffs.y > 0:\n",
+ " return 3\n",
+ " if diffs.x == 0 and diffs.y > 0:\n",
+ " return 4\n",
+ " if diffs.x < 0 and diffs.y > 0:\n",
+ " return 5\n",
+ " if diffs.x < 0 and diffs.y == 0:\n",
+ " return 6\n",
+ " return 7"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "addf716b-892c-4f7f-b71f-c6af8779dff7",
+ "metadata": {},
+ "source": [
+ "I will use the getter provided by my engine, which queries various statistics about all agents in the game:\n",
+ "1. An array of head positions\n",
+ "2. An array of all tail locations\n",
+ "3. The goal location"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "7b5f0d57-8e26-4f95-b7ea-a6810936ad5d",
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "([Point(x=0, y=160)], [0, Point(x=0, y=160)], Point(x=0, y=320))"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "game_engine.get_heads_tails_and_goal()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d3fd47ce-55fe-4d2f-9147-8848193f7ca1",
+ "metadata": {},
+ "source": [
+ "Now to use sense_goal as an index into our q table:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "85e2bab1-c98b-400e-be41-a47ccd4bc163",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def index_actions(q, id):\n",
+ " '''\n",
+ " given q, player_id, an array of heads,\n",
+ " and the goal position,\n",
+ " indexes into the corresponding expected\n",
+ " reward of each action\n",
+ " '''\n",
+ " heads, tails, goal = game_engine.get_heads_tails_and_goal()\n",
+ " state = sense_goal(heads[id], goal)\n",
+ " return state, q[state, :]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "33ae53a8-989a-410c-a04f-2ac76561ce21",
+ "metadata": {},
+ "source": [
+ "Returning state here simplifies some logic later when I train the agent. It will be passed along to my next function, but it can be ignored for now."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "3808c200-2f67-43c4-a80f-c4c17dcfeacf",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([0., 0., 0., 0.])"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "_, rewards = index_actions(q, p1)\n",
+ "rewards"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6a2ef7f7-f6f7-4610-8e98-1d389327f3e8",
+ "metadata": {},
+ "source": [
+ "In our learning agent, these actions will obviously be associated with different expected rewards. But it is not enough to take the best reward, because the positions of the hazards have not been accounted for. I chose to implement a replacement argmin/max function to select actions from this table, which generates new actions in order from highest expected reward to lowest expected reward."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "a172e347-75b7-4b0a-8dcc-07a6ba04f77e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def argmin_gen(rewards):\n",
+ " rewards = rewards.copy()\n",
+ " for i in range(rewards.size):\n",
+ " best_action = np.argmin(rewards)\n",
+ " rewards[best_action] = float(\"inf\")\n",
+ " yield best_action"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "9674f225-e7df-4551-baa8-29eb23fcc1d5",
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0\n",
+ "1\n",
+ "2\n",
+ "3\n"
+ ]
+ }
+ ],
+ "source": [
+ "for action in argmin_gen(rewards):\n",
+ " print(action)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b9bed101-661c-4e61-b8ad-94bbc1900e03",
+ "metadata": {},
+ "source": [
+ "How will we use this? If the action generated is not a viable action, we will take the next best action.\n",
+ "\n",
+ "What if no actions are viable? Then the agent has boxed itself in, and it doesn't matter what action we choose.\n",
+ "\n",
+ "Previously, I reset the snake if it got stuck in a learning-loop. I will instead use epsilon, as it gives me a bit more control over training. Here is my greedy-action selector function, combining the work of all the previous code:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "d3d65397-62a9-47b6-9c84-808282656f80",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def pick_greedy_action(q, id, epsilon):\n",
+ " viable_actions = game_engine.get_viable_actions(id)\n",
+ " state, rewards = index_actions(q, id)\n",
+ "\n",
+ " if np.random.uniform() < epsilon:\n",
+ " return (state, np.random.choice(viable_actions)) if viable_actions.size > 0 else (state, 0)\n",
+ " for action in argmin_gen(rewards):\n",
+ " if action in viable_actions:\n",
+ " return (state, action)\n",
+ " return (state, 0) # death"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2169ac2b-df83-4f53-8a83-b35a2d1a521a",
+ "metadata": {},
+ "source": [
+ "We'll set up epsilon to decay over our 500-step test..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "a5932d47-adbe-46de-b91e-582e40faf369",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_steps = 200\n",
+ "epsilon = 1\n",
+ "final_epsilon = 0.001\n",
+ "epsilon_decay = np.exp(np.log(final_epsilon) / (n_steps))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ee7b066a-330d-4cdd-bbb2-9d2a7ad2ceb4",
+ "metadata": {},
+ "source": [
+ "And watch the snake explore:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "6beff583-e32a-4c15-8fcb-f5b5d45ad548",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for step in range(n_steps):\n",
+ " _, p1_action = pick_greedy_action(q, p1, epsilon)\n",
+ " game_engine.player_advance([p1_action])\n",
+ " epsilon *= epsilon_decay"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4ee2c2e6-933d-460e-b2fc-f5bf1f22e381",
+ "metadata": {},
+ "source": [
+ "This snake obviously has no prior knowledge of how to earn the most reward, but it still does remarkably well because it is not allowed to die. It behaves as expected, favoring up and right when it is not forced to choose a random action.\n",
+ "\n",
+ "Our q_table only has 32 values as a result of removing the 16 danger states... It would be incredibly easy to manually select reward values to fill our q_table with..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "7af51359-872c-4d1b-b178-e27cf86eb3cc",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "set_q = np.array([[-10., -2., 0., -2.],\n",
+ " [-5., -5., 0., 0.],\n",
+ " [-2., -10., 2., 0.],\n",
+ " [0., -5., -5., 0.],\n",
+ " [0., -2., -10., -2.],\n",
+ " [0., 0., -5., -5.],\n",
+ " [-2., 0., -2., -10.],\n",
+ " [-5., 0., 0., -5.]])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "4da4d318-f7e0-412b-8545-8fd346e167b3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "epsilon = 0\n",
+ "for step in range(n_steps):\n",
+ " _, p1_action = pick_greedy_action(set_q, p1, epsilon)\n",
+ " game_engine.player_advance([p1_action])\n",
+ " epsilon *= epsilon_decay"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5c36ab97-2ca0-4468-8d4c-ebd1e4deec23",
+ "metadata": {},
+ "source": [
+ "And the snake already plays optimally, no learning required.\n",
+ "\n",
+ "Now that we have these methods, I will create functions to allow the snake to learn by its own, and then pair it off against the q-table I just built."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0b9968af-0ec2-4b92-a19d-50912703dd4a",
+ "metadata": {},
+ "source": [
+ "### Learning and Temporal Difference"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ce537e44-ac8c-4f09-b89d-a330f13277da",
+ "metadata": {},
+ "source": [
+ "I will be using the temporal difference equation as the key learning element of my reinforcement function. In theory, it allows me adjust the expected reward of a state to agree with its observed successor. In practice, it will allow out agent to take actions that previously led it closer to the goal.\n",
+ "\n",
+ "In order to use this equation, I simply need to create a function that takes the current state/action/outcome, and the previous state/action, as this will be updated in cases where the agent did not reach the goal.\n",
+ "\n",
+ "When the agent does reach the goal, I will manually set that state and action to the best reward, 0. Remember that the q-table is initialized with zeros, meaning untravelled actions are pre-assigned good rewards. Both this and epsilon will encourage exploration.\n",
+ "\n",
+ "Here is the complete function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "a0d3942a-af6a-41f3-be74-167e3abaae0b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def update_q(q, old_state_action, new_state_action, outcome, lr=0.05):\n",
+ " if outcome == multiplayer.CollisionType.GOAL:\n",
+ " q[new_state_action[0], new_state_action[1]] = 0\n",
+ " else:\n",
+ " td_error = -1 + q[new_state_action[0], new_state_action[1]] - q[old_state_action[0], old_state_action[1]]\n",
+ " q[old_state_action[0], old_state_action[1]] += lr * td_error"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "01b21e01-174e-4fdd-ad70-dcc1e6483fb2",
+ "metadata": {},
+ "source": [
+ "Now all that is needed is the training loop. I have high expectations for this agent, so I will only allow it 1500 moves to train itself! Here is where the outputs of pick_greedy_action come in handy:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "95a2ec72-e30c-4730-a876-21f054d3727f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_steps = 1500\n",
+ "epsilon = 1\n",
+ "final_epsilon = 0.001\n",
+ "epsilon_decay = np.exp(np.log(final_epsilon) / (n_steps))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "a71dc022-5e51-46f8-bfed-37b95243fc5e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "p1_old_s_a = pick_greedy_action(q, p1, epsilon) # state, action\n",
+ "game_engine.player_advance([p1_old_s_a[1]])\n",
+ "\n",
+ "for step in range(n_steps):\n",
+ " p1_new_s_a = pick_greedy_action(q, p1, epsilon) # state, action\n",
+ " outcome = game_engine.player_advance([p1_new_s_a[1]])\n",
+ "\n",
+ " update_q(q, p1_old_s_a, p1_new_s_a, outcome)\n",
+ "\n",
+ " epsilon *= epsilon_decay\n",
+ " p1_old_s_a = p1_new_s_a"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c6cbf429-c790-4bb9-8775-ed067844ab4e",
+ "metadata": {},
+ "source": [
+ "The results look promising. Here is everything it learned:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "ad12d31a-a1ec-45af-89b0-f66ca375b524",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([[-5.61286955, -0.92562893, -0.14868602, -0.77936816],\n",
+ " [-4.87612819, -3.84302796, -0.9194685 , -0.46585524],\n",
+ " [-0.65546246, -4.77095018, -0.49342682, 0. ],\n",
+ " [-1.58528747, -6.27145881, -2.59719179, -0.51455945],\n",
+ " [-0.48715079, -1.43421385, -6.12413612, -0.7260854 ],\n",
+ " [-0.78394669, -1.70799532, -3.10876847, -6.45896201],\n",
+ " [-0.798533 , -0.41896323, -1.30809359, -3.63055269],\n",
+ " [-2.96333643, -0.98546563, -2.05542179, -6.07365687]])"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "q"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b2414728-c36c-45d6-8f2d-18e78e482054",
+ "metadata": {},
+ "source": [
+ "### Multiplayer Demonstration, Saving Tables"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "24c73f20-2853-4ba7-a24e-22c5a4b8da5e",
+ "metadata": {},
+ "source": [
+ "The most entertaining way to test the success of my implementation is pair the agents q and set_q against each other. I will first stop and set up a new game:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "id": "a93f0fe5-2bc4-45d7-ba31-2306efaa9806",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Game over!\n",
+ "Game starting with 2 players.\n"
+ ]
+ }
+ ],
+ "source": [
+ "game_engine.stop_game()\n",
+ "p2 = game_engine.add_player()\n",
+ "game_engine.start_game()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "47a1adde-d95d-444e-9688-1290764f8cfa",
+ "metadata": {},
+ "source": [
+ "Now, I can simply call player advance with both player's actions in order, and the engine will handle the rest. I will define a new game loop, similar to the previous one:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "id": "d5b5d089-cb66-47de-b352-36c13fd7dead",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "epsilon = 0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "id": "4de326e6-82dc-48df-8920-d28d0154fbd9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for step in range(n_steps):\n",
+ " # p1\n",
+ " _, p1_action = pick_greedy_action(set_q, p1, epsilon)\n",
+ "\n",
+ " # p2\n",
+ " p2_new_s_a = pick_greedy_action(q, p2, epsilon) # state, action\n",
+ " \n",
+ " game_engine.player_advance([p1_action, p2_new_s_a[1]])\n",
+ "\n",
+ " epsilon *= epsilon_decay\n",
+ " p2_old_s_a = p2_new_s_a"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "820d7e5f-c3e9-4dae-82ce-3c9188d8a8d8",
+ "metadata": {},
+ "source": [
+ "The learned agent normally plays significantly worse than the artificially-learned one, which is okay given I hardly spent time optimizing the number of steps and learning rate. I plan to compare both of the agents again my neural-network approach, so the last this I will do is save the q_tables to a file."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "id": "95227c89-e7db-4923-9160-dacfa1cf4af8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "np.save('superior_qt.npy', set_q)\n",
+ "np.save('inferior_qt.npy', q)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9eec33d8-9a65-426a-8ad5-8ffb2cbe2541",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/revised_snake_q_table_noise.ipynb b/revised_snake_q_table_noise.ipynb
new file mode 100644
index 0000000..695875e
--- /dev/null
+++ b/revised_snake_q_table_noise.ipynb
@@ -0,0 +1,57 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "85da5df2-c926-417c-bd7b-d214ad31ebe1",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "pygame 2.5.1 (SDL 2.28.2, Python 3.11.5)\n",
+ "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
+ ]
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "from collections import namedtuple\n",
+ "from IPython.core.debugger import Pdb\n",
+ "\n",
+ "from GameEngine import multiplayer\n",
+ "Point = namedtuple('Point', 'x, y')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9eec33d8-9a65-426a-8ad5-8ffb2cbe2541",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/superior_qt.npy b/superior_qt.npy
new file mode 100644
index 0000000..8c81fe1
--- /dev/null
+++ b/superior_qt.npy
Binary files differ