|
| 1 | +# Maze Runner |
| 2 | + |
| 3 | +*AI functions can navigate mazes* |
| 4 | + |
| 5 | +In this tutorial, we're going to walk you through how AI functions can interact with games using simply images from the game. To do this, we will use OpenAI's [Procgen Benchmark](https://openai.com/index/procgen-benchmark/), specifically the 2D maze environment and [AI functions](https://www.aifunction.com/) by Weco. We'll build an agent that uses AI functions at it's core. Lets jump right in! |
| 6 | + |
| 7 | +You can follow along through the code and also watch our quick demo on the same: |
| 8 | +<iframe width="640" height="360" src="https://www.youtube.com/embed/DLZ6lhxAFYU" frameborder="0" allowfullscreen></iframe> |
| 9 | + |
| 10 | +<a href="https://colab.research.google.com/github/WecoAI/aifn-python/blob/main/examples/maze_runner.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" width=110 height=20/></a> |
| 11 | +<a target="_blank" href="https://lightning.ai/new?repo_url=https%3A%2F%2Fgithub.com%2FWecoAI%2Faifn-python%2Fblob%2Fmain%2Fexamples%2Fmaze_runner.ipynb"><img src="https://pl-bolts-doc-images.s3.us-east-2.amazonaws.com/app-2/studio-badge.svg" alt="Open in Studio" width=100 height=20/></a> |
| 12 | + |
| 13 | +## Dependencies |
| 14 | + |
| 15 | + |
| 16 | +```python |
| 17 | +%%capture |
| 18 | +!apt-get update -qq |
| 19 | +!apt-get install -y software-properties-common |
| 20 | +!add-apt-repository ppa:openjdk-r/ppa -y |
| 21 | +!apt-get update -qq |
| 22 | +!apt-get install -y xvfb python3-opengl ffmpeg |
| 23 | +!pip install --no-cache-dir aifn Pillow gym procgen pyvirtualdisplay |
| 24 | +``` |
| 25 | + |
| 26 | + |
| 27 | +```python |
| 28 | +import io |
| 29 | +from datetime import datetime |
| 30 | +import base64 |
| 31 | +from copy import deepcopy |
| 32 | +import numpy as np |
| 33 | +import matplotlib.pyplot as plt |
| 34 | +from PIL import Image |
| 35 | +from IPython.display import clear_output |
| 36 | +from pyvirtualdisplay import Display |
| 37 | +from procgen import ProcgenEnv |
| 38 | +from aifn import AIFunction |
| 39 | +import warnings |
| 40 | +warnings.filterwarnings("ignore") |
| 41 | +``` |
| 42 | + |
| 43 | +## Setup Game |
| 44 | + |
| 45 | + |
| 46 | +```python |
| 47 | +def show_frame(observation, log_history): |
| 48 | + """Helper function to display the current frame and command log.""" |
| 49 | + clear_output(wait=True) |
| 50 | + fig = plt.figure(figsize=(10, 7)) |
| 51 | + gs = fig.add_gridspec(1, 2, width_ratios=[3, 1]) |
| 52 | + ax_left = fig.add_subplot(gs[0]) |
| 53 | + ax_left.imshow(observation['rgb'][0]) |
| 54 | + ax_left.axis('off') |
| 55 | + ax_right = fig.add_subplot(gs[1]) |
| 56 | + ax_right.axis('off') |
| 57 | + ax_right.set_facecolor('#f7f7f7') |
| 58 | + ax_right.set_title("Command Log", loc='center', fontsize=12, pad=10, color='black') |
| 59 | + log_text = "\n".join("> " + s for s in log_history) |
| 60 | + ax_right.text(0, 1, log_text, ha='left', va='top', fontsize=10, family='monospace', color='black', wrap=True) |
| 61 | + plt.tight_layout() |
| 62 | + plt.show() |
| 63 | +``` |
| 64 | + |
| 65 | + |
| 66 | +```python |
| 67 | +%%capture |
| 68 | +display = Display(visible=0, size=(1400, 900)) |
| 69 | +display.start() |
| 70 | +``` |
| 71 | + |
| 72 | + |
| 73 | +```python |
| 74 | +# Create our game environment |
| 75 | +env = ProcgenEnv(num_levels=1, start_level=20, use_backgrounds=False, distribution_mode="easy", num_envs=1, env_name="maze") |
| 76 | +``` |
| 77 | + |
| 78 | +## Build Agent |
| 79 | + |
| 80 | +First, head on over to our [platform](https://www.aifunction.com/function/new) and create an AI function with the following description: |
| 81 | +> You are a grey blob navigating a treacherous 2D maze. Your only way out is to follow the dark road that leads to the orange exit. Return an 'action' that follows the path leading to the exit. Use 1 for left, 7 for right, 5 for up and 3 for down. |
| 82 | +
|
| 83 | +<!--  --> |
| 84 | + |
| 85 | + |
| 86 | +Then grab the function name and come right back here to bring your agent to life! |
| 87 | + |
| 88 | + |
| 89 | + |
| 90 | +We now define a class that uses an AI function at it's very core. The agent here is a simple wrapper around our AI function to provide a more agentic like interface for the game we are about to play. The core function takes in an observation (image), preprocesses this image and passes it to our AI function. The AI function then analyzes the image and returns a simple response. We can then extract and perform the action based on the AI function's judgement. |
| 91 | + |
| 92 | + |
| 93 | +```python |
| 94 | +class Agent: |
| 95 | + def __init__(self, fn_name, api_key): |
| 96 | + # Initialize your AI function |
| 97 | + self.get_action = AIFunction(fn_name=fn_name, api_key=api_key) |
| 98 | + |
| 99 | + def __repr__(self): return str(self.get_action) |
| 100 | + def __str__(self): return str(self.get_action) |
| 101 | + |
| 102 | + def act(self, observation): |
| 103 | + # Preprocess the observation |
| 104 | + resized_image = Image.fromarray(deepcopy(observation)['rgb'][0].astype(np.uint8)).resize((1024, 1024)) |
| 105 | + buffer = io.BytesIO() |
| 106 | + resized_image.save(buffer, format="jpeg") |
| 107 | + images = [f"data:image/jpeg;base64,{base64.b64encode(buffer.getvalue()).decode('utf-8')}"] |
| 108 | + action = self.get_action(images_input=images).output["action"] |
| 109 | + return action |
| 110 | + |
| 111 | + def action_to_command(self, action): |
| 112 | + action_space = {1: "left", 7: "right", 5: "up", 3: "down"} |
| 113 | + return action_space.get(action, "unknown") |
| 114 | +``` |
| 115 | + |
| 116 | + |
| 117 | +```python |
| 118 | +# NOTE: Don't forget to set these! |
| 119 | +api_key = "YOUR_API_KEY" |
| 120 | +fn_name = "YOUR_AI_FUNCTION_NAME" |
| 121 | + |
| 122 | +# Initialize our agent that uses an AI function as its brain |
| 123 | +agent = Agent(fn_name, api_key) |
| 124 | +print(f"Agent {agent} is on the job!") |
| 125 | +``` |
| 126 | + |
| 127 | +## Play the Game! |
| 128 | + |
| 129 | +Now you're ready to play the game! |
| 130 | + |
| 131 | + |
| 132 | +```python |
| 133 | +# We'll give the agent 20 timesteps to navigate the maze but you could give it more if you'd like |
| 134 | +timesteps = 20 |
| 135 | +observation = env.reset() |
| 136 | + |
| 137 | +log_history = [f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Agent started navigating the maze"] |
| 138 | +show_frame(observation, log_history) |
| 139 | + |
| 140 | +found = False # Did the agent find the exit yet? |
| 141 | +while timesteps > 0: |
| 142 | + # Determine the agent's next move based on the current state of the environment |
| 143 | + action = agent.act(observation) |
| 144 | + # Observe the results of the agent's action by getting the new state of the environment |
| 145 | + observation, _, done, _ = env.step(np.array([action])) |
| 146 | + timesteps -= 1 |
| 147 | + |
| 148 | + log_history.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Agent says move {agent.action_to_command(action)}.") |
| 149 | + show_frame(observation, log_history) |
| 150 | + |
| 151 | + if done[0]: |
| 152 | + # The agent found the exit! |
| 153 | + found = True |
| 154 | + break |
| 155 | + |
| 156 | +log_history.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Agent found the exit!" if found else f"Agent did not find the exit.") |
| 157 | +show_frame(observation, log_history) |
| 158 | +``` |
| 159 | + |
| 160 | + |
| 161 | +```python |
| 162 | +%%capture |
| 163 | +env.close() |
| 164 | +display.stop() |
| 165 | +``` |
0 commit comments