diff --git a/README.md b/README.md
index 7702ee2..a0843de 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,69 @@
-# q-learning-terrain-navigator
+# Q-Learning Terrain Navigator
+This repository demonstrates the application of Q-learning algorithm in a jupyter notebook environment. The Q-learning algorithm is used to navigate a terrain map and learn the optimal path.
+## Jupyter Notebook
+
+
+
+### Description
+This notebook show how Q-learning works and demonstrate how it can learn to navigate a terrain map and find the optimal path to travel down. This map made using a pygame program that allows you to interactavly draw out the different parts of the terain.
+
+### Features
+- Create a map for the Q-learning algorithm to try using pygame
+- Visualize the map and q-table using a matplotlib
+- Implement a multi-threaded version of Q-learning
+- Compare different hyper paramaters of Q-learning algorithm
+
+### Requirements
+- Python 3.x
+- `jupyter lab` or `jupyter notebook`
+
+```bash
+pip install numpy, matplotlib, threading, tqdm,
+```
+
+
+
+## Pygame Map Builder
+
+
+
+### Description
+This Pygame-based map editor allows users to generate, edit, and save custom maps. Users can define grid sizes, place various terrain types, and automatically apply boundary walls around the grid. The map is saved to a JSON file, and it will load from the file if it exists on startup.
+
+### Features
+- **Adjustable Grid Size**: Use the slider on the right to set the grid size from 5x5 to 15x15.
+- **Terrain Types**: Place different terrain types using keyboard shortcuts.
+- **Boundary Walls**: Automatically creates a boundary with walls (-1000) around the grid.
+- **Save/Load Functionality**: The map is saved to and loaded from `map_data.json`. If you have already created a map it will automatically detect that and load it on start, so you can make simple changes without having to rebuild it again.
+### Controls
+- **Slider**: Adjust grid size (5x5 to 15x15) using the slider on the right panel.
+- **Mouse Click**: Click inside the grid to select a cell and place terrain based on the active key shortcut.
+
+### Terrain Shortcuts:
+- `G`: Place **Goal** (1000)
+- `R`: Place **Road** (-1)
+- `C`: Place **Cliff** (-100)
+- `V`: Place **River** (-10)
+- `M`: Place **Mountain** (-50)
+
+### How to Use
+1. **Start the Program**: Run the Python script to start the map editor.
+2. **Set Grid Size**: Use the slider on the right panel to adjust the grid size between 5x5 and 15x15.
+3. **Edit the Map**: Click on cells and use the keyboard shortcuts to place terrain types.
+4. **Save/Load**: On exiting, the current map is saved to `map_data.json`. The map will automatically load the next time the program is started if the file exists.
+
+### Requirements
+- Python 3.x
+- `pygame` library
+- `numpy` library
+
+### Installation
+Install dependencies using:
+```bash
+pip install pygame numpy
+```
+### Running the Program
+```bash
+./map_generator
+```
diff --git a/docs/map_generator.png b/docs/map_generator.png
new file mode 100644
index 0000000..9df654a
Binary files /dev/null and b/docs/map_generator.png differ
diff --git a/docs/q-table.png b/docs/q-table.png
new file mode 100644
index 0000000..29feeb6
Binary files /dev/null and b/docs/q-table.png differ
diff --git a/images/plot_20241021_224820.png b/images/plot_20241021_224820.png
new file mode 100644
index 0000000..7ca5da6
Binary files /dev/null and b/images/plot_20241021_224820.png differ
diff --git a/images/plot_20241021_224858.png b/images/plot_20241021_224858.png
new file mode 100644
index 0000000..240c1f9
Binary files /dev/null and b/images/plot_20241021_224858.png differ
diff --git a/map_data.json b/map_data.json
new file mode 100644
index 0000000..bcfce64
--- /dev/null
+++ b/map_data.json
@@ -0,0 +1 @@
+[[-1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0], [-1000.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -50.0, -50.0, -1000.0], [-1000.0, -1.0, -100.0, -1.0, -1.0, -1.0, -1.0, -50.0, -50.0, -1.0, -1.0, -1000.0], [-1000.0, -1.0, -100.0, -1.0, -1.0, -1.0, -50.0, -50.0, -100.0, -1.0, -1.0, -1000.0], [-1000.0, -100.0, -100.0, -1.0, -1.0, -50.0, -50.0, -50.0, -100.0, -1.0, -1.0, -1000.0], [-1000.0, -100.0, -1.0, -1.0, -10.0, -10.0, -50.0, -50.0, -1.0, -1.0, -1.0, -1000.0], [-1000.0, -1.0, -1.0, -100.0, -10.0, -10.0, -10.0, -50.0, -1.0, -1.0, -1.0, -1000.0], [-1000.0, -1.0, -100.0, -100.0, -1.0, -10.0, -10.0, -10.0, -50.0, -1.0, -1.0, -1000.0], [-1000.0, -1.0, -100.0, -1.0, -1.0, -1.0, -10.0, -10.0, -10.0, -50.0, -1.0, -1000.0], [-1000.0, -1.0, -100.0, -1.0, -1.0, -1.0, -1.0, -10.0, -10.0, -10.0, -1.0, -1000.0], [-1000.0, 1000.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -10.0, -10.0, -1.0, -1000.0], [-1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0, -1000.0]]
\ No newline at end of file
diff --git a/map_generator b/map_generator
new file mode 100755
index 0000000..21cb746
--- /dev/null
+++ b/map_generator
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+
+
+import pygame
+import numpy as np
+import json
+import os
+
+# Pygame setup
+pygame.init()
+width, height = 800, 600 # Adjust window size to make space for the shortcut panel
+rows, cols = 10, 10 # Default size of the grid (can be changed)
+cell_size = min(width // (cols + 4), height // rows) # Adjust cell size based on window and grid size
+
+# Colors
+colors = {
+ 'wall': (0, 0, 0), # Black for wall (boundary)
+ 'cliff': (255, 0, 0), # Red for cliff
+ 'road': (128, 128, 128), # Grey for road
+ 'goal': (0, 255, 0), # Green for goal
+ 'river': (0, 0, 255), # Blue for river
+ 'mountain': (139, 69, 19), # Brown for mountain
+ 'empty': (255, 255, 255) # White for default
+}
+
+# Create initial map array with a dynamic size
+def create_map(rows, cols):
+ # Create a new map array and set boundary values to -1000
+ new_map = np.full((rows, cols), -1.0) # Default to road (-1)
+ new_map[0, :] = -1000 # Top boundary
+ new_map[-1, :] = -1000 # Bottom boundary
+ new_map[:, 0] = -1000 # Left boundary
+ new_map[:, -1] = -1000 # Right boundary
+ return new_map
+
+# Function to load the map from the JSON file and set the size accordingly
+def load_map():
+ if os.path.exists("map_data.json"):
+ with open("map_data.json", "r") as f:
+ loaded_map = np.array(json.load(f))
+ return loaded_map, loaded_map.shape[0], loaded_map.shape[1] # Return map and its dimensions
+ return create_map(rows, cols), rows, cols # If no file exists, return default map
+
+# Load the map and set the initial size based on the file
+map_array, rows, cols = load_map()
+cell_size = min(width // (cols + 4), height // rows) # Adjust cell size based on loaded grid size
+
+# Create the window
+screen = pygame.display.set_mode((width, height))
+pygame.display.set_caption("Map Editor")
+
+# Slider parameters
+slider_x = width - 100
+slider_y = 350
+slider_height = 200
+slider_pos = slider_y + (slider_height // 2) # Initial slider position
+min_size = 5
+max_size = 15
+slider_value = rows # Default slider value corresponds to the loaded grid size
+
+# Function to draw the grid
+def draw_grid():
+ for row in range(rows):
+ for col in range(cols):
+ value = map_array[row, col]
+ if value == -1000:
+ color = colors['wall'] # Black for walls
+ elif value == -100:
+ color = colors['cliff']
+ elif value == -50:
+ color = colors['mountain']
+ elif value == -10:
+ color = colors['river']
+ elif value == -1:
+ color = colors['road']
+ elif value == 1000:
+ color = colors['goal']
+ else:
+ color = colors['empty']
+ pygame.draw.rect(screen, color, (col * cell_size, row * cell_size, cell_size, cell_size))
+ pygame.draw.rect(screen, (0, 0, 0), (col * cell_size, row * cell_size, cell_size, cell_size), 1)
+
+# Function to display shortcut panel and slider
+def draw_side_panel():
+ font = pygame.font.SysFont(None, 24)
+ shortcuts = [
+ "Shortcuts:",
+ "G: Goal (1000)",
+ "R: Road (-1)",
+ "C: Cliff (-100)",
+ "V: River (-10)",
+ "M: Mountain (-50)"
+ ]
+ for i, text in enumerate(shortcuts):
+ img = font.render(text, True, (0, 0, 0))
+ screen.blit(img, (cols * cell_size + 10, i * 30 + 10))
+
+ # Draw the slider
+ pygame.draw.rect(screen, (150, 150, 150), (slider_x, slider_y, 20, slider_height)) # Slider track
+ pygame.draw.circle(screen, (0, 0, 0), (slider_x + 10, slider_pos), 10) # Slider knob
+ label = font.render(f"Size: {slider_value}x{slider_value}", True, (0, 0, 0))
+ screen.blit(label, (slider_x - 10, slider_y - 30)) # Display current grid size
+
+# Main loop
+running = True
+dragging_slider = False
+
+while running:
+ screen.fill((255, 255, 255)) # Fill the background
+
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ # Save map to file on exit
+ with open("map_data.json", "w") as f:
+ json.dump(map_array.tolist(), f)
+ running = False
+
+ # Handle mouse press on slider
+ if event.type == pygame.MOUSEBUTTONDOWN:
+ mouse_x, mouse_y = pygame.mouse.get_pos()
+ if slider_x <= mouse_x <= slider_x + 20 and slider_y <= mouse_y <= slider_y + slider_height:
+ dragging_slider = True
+
+ # Handle mouse release for slider
+ if event.type == pygame.MOUSEBUTTONUP:
+ dragging_slider = False
+
+ # Handle dragging of the slider
+ if dragging_slider:
+ mouse_y = pygame.mouse.get_pos()[1]
+ slider_pos = max(slider_y, min(slider_y + slider_height, mouse_y))
+ # Map the slider position to a value between min_size and max_size
+ slider_value = min_size + (slider_pos - slider_y) * (max_size - min_size) // slider_height
+ rows, cols = slider_value, slider_value
+ map_array = create_map(rows, cols)
+ cell_size = min(width // (cols + 4), height // rows)
+
+ # Handle mouse clicks for grid drawing
+ if not dragging_slider and pygame.mouse.get_pressed()[0]:
+ x, y = pygame.mouse.get_pos()
+ if x < cols * cell_size: # Only allow clicking inside the grid
+ col, row = x // cell_size, y // cell_size
+
+ # Change the value based on key press
+ keys = pygame.key.get_pressed()
+ if keys[pygame.K_g]: # 'g' for Goal
+ map_array[row, col] = 1000
+ elif keys[pygame.K_r]: # 'r' for Road
+ map_array[row, col] = -1
+ elif keys[pygame.K_c]: # 'c' for Cliff
+ map_array[row, col] = -100
+ elif keys[pygame.K_v]: # 'v' for River
+ map_array[row, col] = -10
+ elif keys[pygame.K_m]: # 'm' for Mountain
+ map_array[row, col] = -50
+
+ # Redraw grid and side panel
+ draw_grid()
+ draw_side_panel()
+ pygame.display.update()
+
+pygame.quit()
diff --git a/q-learning-terrain-navigator.ipynb b/q-learning-terrain-navigator.ipynb
new file mode 100644
index 0000000..19c48ba
--- /dev/null
+++ b/q-learning-terrain-navigator.ipynb
@@ -0,0 +1,577 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Import Packages"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# import necessary libraries\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "from matplotlib import colors\n",
+ "import matplotlib.animation as animation\n",
+ "import json\n",
+ "import time\n",
+ "import threading\n",
+ "import tqdm\n",
+ "from tqdm import tqdm\n",
+ "from tqdm import trange\n",
+ "import datetime"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Create Map\n",
+ "Create a map for the Q-learning algorithm to try. You can choose any grid size, but the larger the grid, the more compute it will take. I would suggest around an 8x8 to 12x12 grid."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "pygame 2.1.0 (SDL 2.0.16, Python 3.10.14)\n",
+ "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
+ ]
+ }
+ ],
+ "source": [
+ "!./map_generator"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Importing Map Array and Displaying Map\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Load the saved map\n",
+ "with open(\"map_data.json\", \"r\") as f:\n",
+ " rewards = np.array(json.load(f))\n",
+ "\n",
+ "#rewards[rewards == 1000] = 500\n",
+ "\n",
+ "environment_rows = rewards.shape[0]\n",
+ "environment_columns = rewards.shape[1]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaEAAAGxCAYAAADLfglZAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAmWUlEQVR4nO3de3RU5b3/8c8QyCRgLiU2CVEIoUWuckuAwz0KRCBgsadViVQQ6kFBJKIUUpAMWBKFysLKbYGCpQjSdRBElyjhIkiBQ0CIHLxQLZAU5QQBEwiYQPL8/nBlfg5JIOhMnlzer7X2H/PsZ+b57pnM/uTZs2ePwxhjBACABfVsFwAAqLsIIQCANYQQAMAaQggAYA0hBACwhhACAFhDCAEArCGEAADWEEIAAGsIoRrmtddek8PhkMPh0AcffFBmvTFGv/zlL+VwOBQfH1+ltWVlZcnhcGjatGkV9vnnP/8ph8OhJ598UpLUvHlzjR49uooqrNjo0aPVvHlzjzZf1nbp0iW5XK5yX8PS1/jEiRM+GdvbTpw4IYfDoddee83dtmfPHrlcLn377bdl+jdv3lxDhw79SWPm5+fr+eefV/fu3RUaGqoGDRooIiJCgwYN0po1a1RYWPiTHv9G4uPjq/z9VVvVt10AfpygoCC9+uqrZd4IO3fu1JdffqmgoKAqr6ljx46KjY3VqlWrNGfOHPn5+ZXps3LlSknS2LFjJUkbNmxQcHBwldZZWb6s7dKlS5o1a5YklXkNExMTtXfvXjVp0sQnY3tbkyZNtHfvXv3iF79wt+3Zs0ezZs3S6NGjFRoa6tXx/vnPf2rQoEHKzc3Vf/3Xf2n69On62c9+pq+//lrvv/++xowZo08//VTPPfecV8eFbxBCNdQDDzyg119/XYsWLfLYUb766qvq0aOH8vPzrdQ1duxYjR8/Xps3by7z325xcbFWrVql2NhYdezYUZLUuXNnG2VWiq3afv7zn+vnP/+5lbF/DKfTqf/4j/+okrGuXr2q4cOH69y5c9q/f7/atGnjsf7+++/XzJkzdejQoSqpBz8dh+NqqBEjRkiS1q5d627Ly8vT+vXrNWbMmHLvM2vWLHXv3l2NGzdWcHCwunTpoldffVXXXsO29HDJhg0b1KFDBwUEBKhFixb6y1/+csO6kpKSFBgY6J7x/NCWLVt06tQpj/quPeRVUlKiP/3pT2rVqpUCAwMVGhqqDh066KWXXnL3Ke/QmSS5XC45HA6PtkWLFqlv374KDw9Xo0aNdOedd2ru3Lm6cuXKDbfl2tri4+Pdh0KvXUoPRZ05c0bjx49X27Ztdcsttyg8PFx33323PvzwQ/fjnDhxwh0ys2bNcj9G6VgVHY5bsWKFOnbsqICAADVu3Fj33XefPv30U48+o0eP1i233KIvvvhCQ4YM0S233KKmTZvq6aefvuEhqilTpigkJETFxcXutokTJ8rhcGjevHnutrNnz6pevXp6+eWX3dvzw+fA5XJpypQpkqSYmJgKDx+/99576tKliwIDA9W6dWutWLHiuvVJ389OP/nkE02fPr1MAJWKjo7W8OHDPdqys7M1cuRIhYeHy+l0qk2bNnrxxRdVUlLi0a+y7xF4DzOhGio4OFi/+c1vtGLFCo0bN07S94FUr149PfDAA1qwYEGZ+5w4cULjxo1Ts2bNJEn79u3TxIkTderUKc2cOdOj7+HDh5WcnCyXy6XIyEi9/vrrmjRpkoqKivTMM89UWFdISIj+8z//U+vWrdOZM2c8/qNfuXKlAgIClJSUVOH9586dK5fLpRkzZqhv3766cuWKPvvss3I/W6iML7/8UklJSYqJiZG/v7+ysrI0Z84cffbZZ5Xa6f3Q4sWLy8wwn332We3YsUOtWrWSJJ07d06SlJqaqsjISF28eFEbNmxQfHy8tm3bpvj4eDVp0kTvvfeeBg0apLFjx+r3v/+9JF139pOenq4//vGPGjFihNLT03X27Fm5XC716NFDmZmZatmypbvvlStXdO+992rs2LF6+umntWvXLj333HMKCQkp8zr/0IABA/TnP/9Z+/fvV48ePSRJW7duVWBgoDIyMtzBsm3bNhljNGDAgHIf5/e//73OnTunl19+WW+++ab7sGLbtm3dfbKysvT0009r2rRpioiI0CuvvKKxY8fql7/8pfr27VthjRkZGZKke++9t8I+1zpz5ox69uypoqIiPffcc2revLneeecdPfPMM/ryyy+1ePFid9+beY/ASwxqlJUrVxpJJjMz0+zYscNIMv/7v/9rjDGma9euZvTo0cYYY9q1a2f69etX4eMUFxebK1eumNmzZ5uwsDBTUlLiXhcdHW0cDoc5fPiwx30GDhxogoODTUFBwXVrLK1r/vz57razZ88ap9NpHnroIY++0dHRZtSoUe7bQ4cONZ06dbru448aNcpER0eXaU9NTTXX+5Mu3eZVq1YZPz8/c+7cues+5rW1XWvevHlGklm2bFmFfa5evWquXLli+vfvb+677z53+5kzZ4wkk5qaWuY+pa/x8ePHjTHGnD9/3gQGBpohQ4Z49MvOzjZOp9MkJSV5bIck8/e//92j75AhQ0yrVq0qrNMYYwoKCoy/v7+ZPXu2McaYf//730aSmTp1qgkMDDTfffedMcaYRx991ERFRbnvd/z4cSPJrFy5ssxzU7oNPxQdHW0CAgLMyZMn3W2XL182jRs3NuPGjbtujYMGDTKS3LWUKikpMVeuXHEvV69eda+bNm2akWT+53/+x+M+jz/+uHE4HObzzz8vd6zrvUf69et33fcXKo/DcTVYv3799Itf/EIrVqzQkSNHlJmZWeGhOEnavn27BgwYoJCQEPn5+alBgwaaOXOmzp49q9zcXI++7dq1c39uUyopKUn5+fn66KOPKlXXDw/Jvf766yosLLxufZLUrVs3ZWVlafz48Xr//fd/8mdbhw4d0r333quwsDD3Nj/88MMqLi7WsWPHfvTjrl27Vn/4wx80Y8YMPfroox7rli5dqi5duiggIED169dXgwYNtG3btjKHzipr7969unz5cpkz9Zo2baq7775b27Zt82h3OBwaNmyYR1uHDh108uTJ647TsGFD9ejRQ1u3bpX0/awjNDRUU6ZMUVFRkXbv3i3p+9lRRbOgyurUqZN7tiFJAQEBuuOOO25YY0VeeuklNWjQwL388G93+/btatu2rbp16+Zxn9GjR8sYo+3bt3v0rex7BN5BCNVgDodDjzzyiFavXq2lS5fqjjvuUJ8+fcrtu3//fiUkJEiSli9frn/84x/KzMzU9OnTJUmXL1/26B8ZGVnmMUrbzp49e8O6xowZoyNHjujAgQOSvj8UFxMTo7vuuuu6901JSdGf//xn7du3T4MHD1ZYWJj69+/vfpybkZ2drT59+ujUqVN66aWX9OGHHyozM1OLFi2SVHabK2vHjh0aPXq0Hn744TJnYM2fP1+PP/64unfvrvXr12vfvn3KzMzUoEGDfvR4pc93eWfLRUVFlXk9GjZsqICAAI82p9Op77777oZjDRgwQPv27VNBQYG2bt2qu+++W2FhYYqNjdXWrVt1/PhxHT9+/CeHUFhYWJk2p9N5w+eoNLiuDaukpCRlZmYqMzNTXbp08Vh39uzZCp+70vXSzb9H4B2EUA03evRoffPNN1q6dKkeeeSRCvu98cYbatCggd555x3df//96tmzp+Li4irsf/r06QrbytuBlFeXn5+fVqxYoaysLB06dEhjxowpc+LAterXr6/Jkyfro48+0rlz57R27Vrl5OTonnvu0aVLlyR9/19zeR+yf/PNNx63N27cqIKCAr355psaOXKkevfurbi4OPn7+9+w/op8/PHHGj58uPr166fly5eXWb969WrFx8dryZIlSkxMVPfu3RUXF6cLFy786DFLn++vv/66zLqvvvpKt956649+7Gv1799fRUVF2rVrl7Zt26aBAwe62zMyMtyfyfTv399rY96M0no2bdrk0R4eHq64uDjFxcWV+XpCWFhYhc+dJPfzd7PvEXgHIVTD3XbbbZoyZYqGDRumUaNGVdjP4XCofv36Ht/duXz5sv72t7+V2//o0aPKysryaFuzZo2CgoLK/KdZnqioKA0aNEhr167VokWLVK9evevWV57Q0FD95je/0YQJE3Tu3Dn32WLNmzdXbm6u/u///s/dt6ioSO+//77H/UsDz+l0utuMMeWGR2VkZ2dr8ODBatGihdavX68GDRqU6eNwODzGk74Prr1793q0lfapzH/XPXr0UGBgoFavXu3R/u9//1vbt2/3aiB069ZNwcHBWrBggU6fPu3e6Q8YMECHDh3S3//+d7Vt29Y9i6jIzWzfzbjvvvvUtm1bpaWl6bPPPqvUffr3769PPvmkzGHkVatWyeFwuGfnN/segXcQQrXA888/r40bN173y42JiYm6ePGikpKSlJGRoTfeeEN9+vQps8MsFRUVpXvvvVcrV67Ue++9p5EjRyojI0MzZsxQw4YNK1XX2LFj9e233+qVV15RQkKCmjZtesP7DBs2TCkpKVq/fr127dqlv/3tb1qwYIGio6PdZ4A98MAD8vPz04MPPqh3331Xb775phISEjxOLZa+/6/Z399fI0aM0ObNm7Vhwwbdc889On/+fKXqv9bgwYP17bffaubMmTp69Kj27dvnXs6cOSNJGjp0qLZs2aLU1FRt375dS5Ys0T333KOYmBiPxwoKClJ0dLTeeustbdmyRQcOHKjwCgmhoaF69tlntWnTJj388MPavHmzVq9erbvuuksBAQFKTU39UdtTHj8/P/Xr109btmxRTEyM+wuovXr1ktPp1LZt2yp1KO7OO++U9P1nNXv37tWBAwd+0mzwh/Vt3LhRoaGh6tatm55++mm9/fbb2r17t9555x09++yzysrK8vju3FNPPaXbbrtNiYmJWr58ubZs2aJJkyZp8eLFevzxx3XHHXdIuvn3CLzE9pkRuDk/PDvueso7O27FihWmVatWxul0mhYtWpj09HTz6quvljmLKTo62iQmJpr//u//Nu3atTP+/v6mefPmHme7VUZRUZGJiIgo92ytH471wzPQXnzxRdOzZ09z6623Gn9/f9OsWTMzduxYc+LECY/7vfvuu6ZTp04mMDDQtGjRwixcuLDcs+Pefvtt07FjRxMQEGBuu+02M2XKFLN582YjyezYscPdrzJnx0mqcCk9M6ywsNA888wz5rbbbjMBAQGmS5cuZuPGjeU+/tatW03nzp2N0+k0ktxjXXt2XKlXXnnFdOjQwfj7+5uQkBDzq1/9yhw9etSjz6hRo0yjRo3KPM83OnPwh1566SUjyTz66KMe7QMHDjSSzKZNmzzayzs7zhhjUlJSTFRUlKlXr57H813693WtmznjLC8vz6SlpZmuXbua4OBgU79+fRMeHm4GDhxoFi1aVOYMzpMnT5qkpCQTFhZmGjRoYFq1amXmzZtniouLPfpV9j3C2XHe4zCGb2HBU/PmzdW+fXu98847tksBUMtxOA4AYA0hBACwhsNxAABrmAkBAKwhhAAA1hBCAABrqt1POZSUlOirr75SUFDQDS/xAgCofowxunDhgqKiolSv3vXnOtUuhL766qtKfbMeAFC95eTk6Pbbb79un2p3OO7aiw8CAGqmyuzPq10IcQgOAGqHyuzPq10IAQDqDkIIAGANIQQAsIYQAgBYQwgBAKwhhAAA1hBCAABrCCEAgDU+C6HFixcrJiZGAQEBio2N1YcffuiroQAANZRPQmjdunVKTk7W9OnTdejQIfXp00eDBw9Wdna2L4YDANRQPvll1e7du6tLly5asmSJu61NmzYaPny40tPTr3vf/Px8hYSEeLskAEAVy8vLU3Bw8HX7eH0mVFRUpIMHDyohIcGjPSEhQXv27CnTv7CwUPn5+R4LAKBu8HoIffPNNyouLlZERIRHe0REhE6fPl2mf3p6ukJCQtwLP+MAAHWHz05MuPbqqcaY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",
+ "text/plain": [
+ "