Super Monkey Ball AI using NEAT and YOLO
This repository contains the excessive work completed in an attempt to create an artificial intelligence agent in the game Super Monkey Ball. Check out this video on the project.
This project utilizes two technologies: NEAT and YOLO. The game is expected to be played on the Dolphin Emulator.
NEAT (Neural Evolution of Augmenting Topologies) is the primary mechanism utilized in this projects. To facilitate the evolution, the aiai_ai.py
script runs the neat-python
library. After a genome is initialized, a screenshot is taken at each interval in the script. This screenshot is then analyzed by the generated model and an action is determined accordingly. To determine the resulting state's fitness, the screenshot is evaluated to see if it is one of three states [TIME OVER, FALL OUT, GOAL]
.
In the GOAL
state, the genome's fitness is determined by how quickly it reached the Goal, based on the following equation: 30 + 1.25 * time_remaining
where the fitness of the GOAL
state is between [30, 105]
.
The TIME OVER
and FALL OUT
states are more complicated to calculate. A problem arose of how to have a more granular fitness beyond a simple state check. To resolve this, it was determined to include a value representing how close the genome made it to the Goal before failing (see the YOLO section for more information). If a genome got as close to the Goal as possible, but still failed, it would be awarded a fitness score of 50. If the end-state was TIME OVER
or FALL OUT
this score would be modified by -25 points.
The highest fitness score during a given genome's runtime will be the score evaluated during population reproduction.
YOLO (You Only Look Once) is used as a metric of determining how close a given genome is to the Goal. In short, the YOLO model detects the size and location of the Goal (if one is present), returning a bounding box of the Goal. The area of the bounding box is used to calculate the percentage of the screen the Goal occupies. If the Goal occupies at least 40% of the screen, the given genome will be given the max reward possible for TIME OVER
or FALL OUT
states.
The design of this project was largely inspired by SethBling's MarI/O. The inspiration of starting this project came from my wife holding three World Records in Super Monkey Ball: Banana Mania speedruns (as of April 4th, 2022). I simply wanted to feel better about my inadequacy in this series.
The purpose of this project is to create a Super Monkey Ball AI that can reasonably beat standard stages in the game.
AiAi AI was tested on three stages: Beginner Floor 1, Beginner Floor 2, and Beginner 7.
All results were conducted on the following specs
Windows 10
NVIDIA RTX 3060
AMD Ryzen 5 3600 6-Core Processor 3.95 GHz
16 GB RAM
With these specs, and default configurations, the AI is able to analyze the game at approximately 30 FPS, essentially every other frame of the game.
So far, the AiAi AI has been tested on beginner floors 1, 2, and 7. These were chosen for their relative simplicity in layout, allowing for an easier evolution for the population.
Due to it's nature, Floor 1 is the simplest stage, only requiring the player to move straight ahead. Because of it's simplicity, it did not take long for the population to surpass the max expected fitness score of 101
. Therefore, to gain further information about the population's evolution, a second run was conducted to see how high the average fitness for the population could achieve. The results are graphed below.
Beginner Floor 2 adds complexity to Floor 1 by having a gap in the middle of the stage. As the graph shows, it took the population a significant amount of generations to reach a higher maximum fitness. For this stage, the expected fitness threshold was 99.5
. However, the population only reached a maximum of 98.76
. After 323 generations, the process was halted.
Floor 7 was a significant increase in complexity to test the overall capabilities of the system. The expected fitness threshold was set to 90
. However, the population never officially crossed the goal. Despite this, there was significant progress made towards reaching the goal. The population almost reached the goal several times, but not close enough to get the fitness score out of the negatives.
To utilize this project, be sure to have a working version of the Dolphin Emulator installed on your machine, along with a ROM of Super Monkey Ball for GameCube. The project can be downloaded onto your local machine using the following command:
git clone https://github.com/AlexTheM8/AiAi-AI.git
For best results, the following settings should be configured for the Dolphin Emulator.
Auto
or Force 3:4
Auto-Adjust Window Size
should be checkedNative 640x528
Keep Window on Top
should be checkedShow On-Screen Display Messages
should be un-checkedShow Active Title in Window Title
should be checkedPause on Focus Loss
should be un-checkedThe code of this project was programmed using Python 3.9.0
on a Windows 10 machine. It is currently unknown if this project is functional in any other Python version or OS.
Once downloaded, navigate into the project folder and execute the following command to install the Python dependency libraries:
pip install -r requirements.txt
If necessary, additional requirements can be installed by executing the following command:
pip install -r ./yolov5/requirements.txt
Prior to executing the program, the virtual gamepad needs to be initialized. To do so, the controller.py
must be configured executed.
Pulling up the Dolphin hotkey controls, navigate to the Save and Load State
tab (see below).
Once there, execute the following command:
python controller -s load
This will set the controller to repeatedly press the virtual button designated to loading the game's state. Follow these steps to configure the Load State
hotkey.
Device
dropdown, select the virtual gamepad (multiple devices may be listed, so repeat steps 2-4 with a different device until the correct device is found)Load State Slot N
(where N can be any number you choose. Remember this number) boxButton 3
to appear in the boxButton 3
flash bold multiple timescontroller.py
processProfile
in Dolphin to avoid the need to repeat this process in the futureFor the joystick controls, navigate to the Dolphin controller config menu.
To configure the joystick in Dolphin, follow these steps:
python controller -s up
Device
corresponding to the virtual gamepad (as described in Hotkey Config, the device is not universally labeled, so steps 3-5 may need to be repeated until device is found)Control Stick
section, click the box next to Up
Axis Y-
to appear in the boxAxis Y-
flash bold multiple times along with a visual representation of the joystick moving in the visual abovecontroller.py
process[down, left, right]
, replacing each instance of "up" with the corresponding direction (Axis Y+
for down, Axis X+
for left, Axis X-
for right)Profile
in Dolphin to avoid the need to repeat this process in the futurepython controller -s random
. The virtual joystick should be moving appropriatelyThe evolution process requires a save state to be accessible as this is how the agent resets the game state upon starting a new genome. Please refer to Hotkey Config before configuring the save state.
Save Slot
chosen in the Hotkey Config step (Note: for a more-precise save state, use the frame-advance TAS tools provided by Dolphin)controller.py
to test if the save state (and corresponding hotkey) are configured appropriately (see Hotkey Config).Once all the previous steps are completed, launch the Super Monkey Ball ROM on Dolphin and execute the following command:
python aiai_ai
From there, you should see the program load up the save state and begin evolution.
IMPORTANT: After starting evolution DO NOT MOVE THE EMULATION WINDOW. If you want to move the window, restart the program.
To customize behavior, there are some options and features available in the AiAi AI. These options include logging and stat-tracking as well as network customization options.
To customize the amount of messages displayed when running, there are three logging options: [FULL, PARTIAL, NONE]
. The FULL
logging option (enabled by default) will display all available logs during evolution. The PARTIAL
logging option will display the max fitness of the most-recently executed genome along with the default NEAT logs. The NONE
logging option will only display the default NEAT logs.
python aiai_ai --logging full
-l partial
none
Along with the evolution logging, there is a stat-tracking document that saves the max fitness, mean fitness, and standard deviation of each generation (saved as a CSV file labeled stats.csv
). This file is enabled by default, but can be disabled as an execution parameter.
python aiai_ai --stats
-s
To speed up evolution, there is a feature where a given genome will be terminated early if it is not moving for too long. This is enabled by default, but can be disabled at execution. NOTE: feature should be disabled for stages that may require the player to wait.
python aiai_ai --zero_kill
-z
On Windows systems, a magnification can be applied to the display. Because of this, there is a runtime argument to set the magnification of the used display.
python aiai_ai --window_scale 1.0
-w 1.0
Scale must be set as a float
(ex. 1.0, 1.25, 1.5, etc). NOTE: Scale has only been tested for 1.0 and 1.25
As part of the NEAT library, the neural network can be customized in a number of ways. For a full description of all customization options, see the NEAT documentation. Some of these options in the config-feedforward
document will be highlighted here.
In the [NEAT]
section, the fitness_threshold
can be adjusted for the given stage where the fitness can be between [-50, 105]
. The pop_size
, or "population size" can be customized, where this number will correspond to how many genomes are in each generation.
Under the network parameters
category of the [DefaultGenome]
section, the num_inputs
will be dependent on your window configurations. Best way to know what the num_inputs
should be set to is by running the AiAi AI program (see Launch AI Agent) and change the value to the received value, in case of execution failure. See the example below.
File "c:\neat-python\neat\nn\recurrent.py", line 27, in activate
raise RuntimeError("Expected {0:n} inputs, got {1:n}".format(len(self.input_nodes), len(inputs)))
RuntimeError: Expected 1425 inputs, got 5967
In this example, num_inputs
should be set to 5967
The YOLO goal-detection is the primary method of determining a more granular fitness. The main method of customization is through the model training. Please refer to the YOLO documentation for more information on training customization. To ensure the goal-detection matches the YOLO model, the size
parameter in the following line in aiai_ai.py
should match the specifications made in training.
ref = model(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), size=320)
Below is a list of known issues with the current project.
neat-python
library, there is an occasional issue with population reproduction after a species stagnates where an assert with the node_dict
will fail. This is due to a naive assert check for the stored indexer. The fix can be found on the Revisions
branch of my personal fork of the library, which can be pulled and installed locally.neat-python
library is that the Checkpoint
feature does not save the best genome after each generation. Whether this is an intentional design is debatable. However, this requires the user to specify a fitness threshold for the system to exceed to consider it a "solved" problem. In the design of this project, a stage-specific max fitness was not intended. Instead, it was largely expected for the evolution to find the best potential solution before stagnating and going extinct. This issue can be resolved by either setting an expected max fitness, or by utilizing the changes on the Revisions
branch of my personal fork of the library.