Hello, if you have any need, please feel free to consult us, this is my wechat: wx91due
Task Overview
Your objective is to help Mario overcome a series of increasingly tricky obstacle courses before time runs out. Stomp on the enemies, jump over pits and grab the powerups on your way to the flagpole that awaits at the end of each level!
Your controller must use evolutionary principles: parent selection, breeding and mutation, a fitness function, population selection, and genotypes. How you implement these ideas is up to you:
- You are free to consider any features you like from the game environment (e.g., as part of your genotypes).
- You are free to use any kind of evolutionary process (e.g., fixed-length, variable-length and evolutionary programming)
- You can combine evolution with other strategies (e.g., neural networks)
Task 1: Beat Level 1-1 (Submission A)
4. Getting Started
Starting Off
You will most likely get a message up the top that says “Project JDK is not defined”, click on Setup SDK, and then select Add SDK->Download JDK. Then when the window pops up, install JDK Version 21.With that, open up src->PlayLevel and then hit the Play button in the top right of the screen, this should start level 1-1 with a human controller (you!)
The controls are:
Have some fun trying to beat 1-1, you’ll be dealing with this level a lot over the next few weeks!
Creating a Controller
First, create a Package (folder) inside of the agents named EAController:
Then, right click inside this new folder and create a New Java Class named Agent. With this, you should have a blank class called Agent.
After that, at the end of the class declaration, add implements MarioAgent(You will need to add an import for engine.core.MarioAgent). This makes it so your new agent actually has the functions we need.
Speaking of functions, add the following four overridden functions:
To actually test it, head into PlayLevel.java and then change the Agent passed in in runGame to be agents.EAController.Agent() and then hit play! You should see Mario just standing there doing nothing until the timer runs out. This is because we haven’t given him any inputs!
Implementing Your Controller
These are the only four functions we need to override, and will do all of the work we need it to do. The game can run in one of two modes, “assessed”, and “training” mode. The mode the game is in impacts what user defined functions are called. More details about these modes can be found in Section 5: The Game Loop.
The four functions that you will need to implement are:
- initialize acts as a constructor/beginplay function here, where you can initialise/reset any variables.
- This could include any minor pre-training / setting of initial genotypes.
- getActions is the function that needs to return a list of 5 boolean values representing the buttons on a physical controller that it is pressing, in order they are [LEFT, RIGHT, DOWN, RUN, JUMP]. By default, each of these are set to false. This function is called every single tick to be able to figure out what buttons are being pressed each frame.
- getAgentName just returns the name of the controller.
- train is a function that gets called only when the assessedMode variable inside of
- MarioGame is set to false. This will allow you to do any pre-training you want to do, which might be something like running a bunch of evolutionary controllers all the way through the level on repeat to generate better genotypes!
If we wanted to make Mario only run to the right of the screen, we could change the getActions to return instead:
5. The Game Loop
This section explains the main game loop. You will not need to modify this code, but you may find it helpful to understand how it works. This code is found in MarioGame.java.
First here the game creates a new world and sets up the level with a game timer of however much is specified in the timer parameter. If the visual boolean is set to true, then it will actually create a screen we can see, and if not the game is run without a GUI. After that, it sets Mario’s state to match what we specify in the marioState parameter, then the graphics are actually initialised and created.
Next we get the current system time as the start time, and create a new MarioTimer.
MarioTimer is responsible for how long the agent is allowed to take on each action, and if it takes too long, it will abort the operation. After this, we run the agent’s initialize functionand pass in a new MarioForwardModel (this class will be important later -- see Section 6) as well as the MarioTimer we just created. Lastly here we call the train function if assessedMode is false, letting you train your agents if you would like for as long as you like.
Now for the actual “loop”, here we check to see if the game is running and it’s not paused, if so, then we tick the real-time timer if we’re in assessed mode, and if it gets to the maximum amount of time, it changes the state to REAL_TIME_OUT, stopping the level.
Then, we create a new timer to time the agent out this tick if they take too long. Then we actually call the getActions function from our agent, getting a boolean array of actions.
After that, we check to see if the MarioTimer passed in is taking longer than one frame should take, and if it does, we print how long extra the getActions function is taking to return than the amount of time left in the frame. For example, if we were running 30 FPS, we would have 33.33ms per frame to run on time, any longer than that and the game slows down!
Next, we actually update the world based on Mario’s actions, then add all of the events that happened last frame to our arrays we declared above the while loop. After this, we check to see if we should render the game, and lastly here we check to see if the game is going too quickly and slow it down by an appropriate amount if it is. This is because the game should be running at the specified FPS (30) and if we return our actions super quickly, we need to slow the game down to maintain this 30 FPS.
Lastly we return our MarioResult so we can print the results out for our stats!
6. What is a MarioForwardModel?
NB: Make sure that you’re creating a copy of the MarioForwardModel for each simulated playout by using the Clone function!