This is Math Racer, a sluggish EV3 racer that gains turbo boosts whenever the user provides correct answers to Alexa's simple arithmetic quiz! The aim of the game is to ram the car in front!
Video demonstration
Description:
Math Racer pits the EV3 robot racer against a competing car.
The EV3 robot will run the race course automatically using a PD algorithm. The opponent's race car in this scenario is a simple RC Technic car controlled by another human player.
Once the game begins, Alexa will start asking simple arithmetic questions. If the player answers correctly, Alexa will provide a temporary speed boost to the EV3 robot.
A wrong answer however, will result in a temporary speed penalty. In addition to chasing down the opponent, the player will also have to race against the clock! Inspired by the classic game outrun, every 4 correct answers will grant a time extension on the clock. However, every time extension will also increase the difficulty of the questions Alexa asks.
The game is won when the EV3 robot reaches the opponent car to trigger the front-mounted touch sensor. The game is lost when the timer runs out.
SetupBuild EV3 Math Racer.
Note on cables: Unfortunately, cable connections are not shown in the building instructions due to technical issues on my end.
The build in the building instructions is kind of "over-engineered" for the current iteration of math racer where you only need to follow one wall and bump into the opponent in front. The original design of the project included changing lanes, hence why the wall-detecting sensor is mounted on a turntable. I will go over this in more detail in the designer notes at the end.
That being said, I used flexible cables like the ones you can purchase from mindsensors.com for the sensor on the turntable. The stock cables are quite stiff so they won't allow your turntable assembly to rotate as freely. Of course, if you never plan to utilize the turntable function and plan to always point the sensor to either the left or the right, you can use the stock cables. Or you could also just devise a much simpler mount and skip the medium motor assembly all together.
The instructions for the most part are pretty straightforward. The base is based on the EV3 educator robot (retail kit version). The front medium-motor mount as well as the EV3 brick mounting points have been moved back by one stud, so it's not the exact same.
Prepare opponent vehicle.
In theory, you can use just about anything as an opponent vehicle. I built a standard Technic car that is controlled by infrared remotes.
One advantage of using the Power Functions system is the ability to use the IR Speed Remote Control (#8879). With this, you have better control over the speed of your opponent vehicle. If you can't consistently control and set the desired speed, you will find it either incredibly difficult or easy to win the game. For steering, I used the base IR Remote Control (#8885) because I found it easier to be able to just turn all left or all right instead of controlling it via the wheel in the other remote.
With all this in mind, you could just as well build a second autonomous wall-following EV3 vehicle if you have one lying around. For one, this would allow you to just concentrate on answering the math quiz. This would also make it way easier to set the speeds of both players on the track to your liking.
Build your track.
I used poster cardboard to build my track. It's not anything fancy, you just need to make sure the final height is high enough for your wall-sensor to give a consistently accurate reading. Since my wall-sensor is positioned quite low to the ground, I only need a wall-height of 7cm. I taped sections of the wall together using scotch tape and taped Duplo blocks to the back to keep the sections upright. Paper clips can be used to help keep the joining sections of pieces straight. You definitely want to do a good job here and not be too sloppy with your craftsmanship. The quality and placement of your wall can make or break your experience.
Load Math Racer code into the EV3.
The source code for the EV3 can be found in the source-code section below and consists of the base EV3 python source code as well as the ini file that's needed to interface with the Echo device properly.
For the ini file, you will need to replace the "xxxxx" sections with your Amazon ID and Alexa Gadget Secret code.
I used visual studio code for the project as it's the easiest way to upload and run the code on the EV3
The main values you'll likely want to tweak here is the "race_power" and possibly the code in the power_modulate function. Some trial and error will be needed to balance out the speed with the opponent car's speed (hence why the speed remote for the opponent car is useful here).
#--- Input ---
self.race_power = 55
self.add_power = 0
self.target = 8
self.kp = float(25)
self.kd = float(6)
self.minRef=0
self.maxRef=self.target*2
You could also try different kp and kd values for the PD algorithm to try and get a smoother driving response. I definitely didn't fine tune these values enough to get the ultimate wall-follower. In fact, I spent pretty much most of the time using the "wrong" values until only 1 week before the contest closed.
I based my earlier settings on various EV3 line-follower algorithms. However, keep in mind that a color sensor will go through a value of 0-1 much quicker when reading a line as opposed to a distance sensor detecting a wall. Hence the values that you get from a line-follower algorithm may not work right off the bat.
In the original video, the EV3 gets pretty wild swerving left and right. In the video below, you can see the EV3 a lot more "stable" after using the kp and kd values in the above code snippet (kp=25, kd=6). As always, there's room for improvement, but the best values will also depend on your track configuration.
Create Alexa skill
The lambda back-end for this has been written in python. As such, when you're creating a new Alexa skill for this, you will want to ensure that you choose "Alexa-Hosted (Python)".
Copy and paste the JSON contents from the source code below to generate the proper JSON model.
Remember to also turn on the Custom Interface Controller toggle under the Interfaces menu.
Another point worth mentioning at this point, you will want to make sure your skill is enabled for your Echo device. I did this through my phone's Alexa app. If you don't enable it, Alexa will have no idea what you're talking about when you say the invocation name. I literally spent a whole weekend troubleshooting before I realized this!!!
The bottom section of the code is where you will find the core math generator function. You may opt to tweak this code to only do addition if you wanted to focus on just addition, or you could add in division if you wanted to up the challenge. I didn't want to deal with decimal numbers in Alexa so I steered clear of adding division.
def rand_math(tot_correct):
operations = ("+","-","x")
op = choice(operations)
max_rand_val=5+(math.floor(tot_correct/4)*5)
max_rand_val=min(max_rand_val,40)
num_1 = randint(0,max_rand_val)
num_2 = randint(0,max_rand_val)
if op == "x":
num_1=num_1%13
num_2=num_2%13
holder=0
if num_1<num_2:
holder = num_1
num_1=num_2
num_2=holder
if op == "+":
answer = num_1+num_2
op_word = "plus"
elif op == "-":
answer=num_1-num_2
op_word = "minus"
elif op == "x":
answer = num_1*num_2
op_word = "times"
return (num_1,num_2,op_word,answer)
Whenever Alexa calls the rand_math function to generate an arithmetic question, it stores the answer into a session attribute. When the player responds with an answer to the question, Alexa compares it to the stored session attribute to determine if the answer is correct or not. The total number of correct answers is also adjusted to determine whether to extend the time limit.
actual_answer = session_attr.get('answer_value')
answer_given = int(handler_input.request_envelope.request.intent.slots[
"number"].value)
if answer_given == actual_answer:
buzz_sound = CORRECT_BUZZ
result='correct'
session_attr['total_correct']=session_attr.get('total_correct')+1
tot_correct = session_attr.get('total_correct')
if tot_correct>0 and tot_correct%4==0:
refill_text = "Time extended. "
extend=True
else:
extend=False
elif answer_given != actual_answer:
buzz_sound = INCORRECT_BUZZ
result='incorrect'
A payload is sent to the EV3 instructing it to change speeds and to extend the timer (if applicable).
payload = {
"type": "mathResult",
"result":result,
"extend":extend
}
On a side note, the original mission-04 tutorial had gameshow music running after every intent response was executed. I didn't actually realize til later that this time extension "trick" also prevented me from answering Alexa properly.
This does mean that passing the allotted 8+8 seconds (for .speak and .ask) will cause the session to end, but if you're waiting that long to answer, you're not winning the game. :)
Similarly, I used quick buzz sounds from the sound library instead of Alexa announcing "correct" or "incorrect" to minimize waiting time before the next question was asked. The aim of the game is to answer questions quickly, thus a lot the the responses was designed with that in mind.
Run Math Racer
After you have booted up EV3, run the core ev3 code through Visual Studio Code (or SSH if you fancy). After the EV3 links up with your Echo device via Bluetooth, you can now call up the skill.
"Alexa, open Math Racer"
Alexa will welcome you, the contestant, and ask if you're ready to go. You will need to initiate the race by giving the "ready to start" intent.
"Ready to start"
At this point, your EV3 will start to run and Alexa will begin asking you arithmetic questions. Each correct answer will provide a temporary speed boost, while a wrong answer will give you a temporary speed penalty.
If you are successful in making contact with the vehicle in front, you will win the game! But if you take too long, the EV3's timer will run out and the game will end.
Designer notesLane-changing functionality
The original project idea revolved around motorsport racing, with the player as a pit crew chief, providing instructions to the EV3. Pitting was originally envisioned, with a gas-meter slowly ticking away showing how much gas was remaining. The player would ask the EV3 how much gas was remaining, direct it to pit, or attempt overtakes on the opponent.
However, by mid-Dec, the project looked more and more like a tech demo. While the idea seemed like an interesting use of interacting with Alexa, as if communicating with someone, it wasn't fun or engaging to play. That's when the idea of incorporating a math quiz took flight. Luckily, this new direction was more of an evolution of what was already done, requiring minimal rebuilding and coding.
As a result of tightening the scope of the project, the changing lane aspect of the EV3 got tossed. Having to actually change lanes to overtake the opponent really didn't add anything to the game as opposed to just bumping into it which seemed a lot more intuitive. In fact, having to overtake on the outer lane increased the difficulty of the game as the EV3 has to travel a longer route.
The video below (apologies for not being the best shooting quality) shows the lane-changing feature in action. You can see the sensor actually rotates quite early on since the opponent vehicle is quite close to the EV3 at the start. For the opponent car, I installed side "flags" and taped a green sticker sheet to it (I didn't have enough green beams). The idea is that the rear-mounted color sensor on the EV3 points to the left at all times and if it detects the green flag, that would trigger a win condition. But as you'll see, it's a bit more challenging (though better-optimized kp/kd values here could've helped).
That being said, if you ever feel like making use of lane-changing, you'll need a few things in your EV3 python code. For one thing, you'll need a bunch more states for the EV3 to cycle through. Each flag corresponds with a specific action the EV3 needs to perform while doing a lane change. As you can see, there's 3 flags needed per lane change.
self.left_hug = False
self.left_veer = False
self.straight_to_left = False
self.right_hug = False
self.right_veer = False
self.straight_to_right = False
You will need some sort of trigger to activate a particular flag like turning right to initiate a lane change. Lets say instead of the touch sensor, you mount another infrared sensor which I'll instantiate as ir_seeker that's looking for a beacon on the opponent car.
When the infrared sensor picks up the beacon and meets some arbitrary condition, self.right_veer is activated while self.left_hug is deactivated. There should only be one active flag at any time.
def _seek_thread(self):
while True:
seeking = self.ir_seeker.heading_and_distance(channel=1)
if seeking[1] and self.race_mode:
if seeking[1]<12.0:
if self.left_hug and self.course==0.0:
self.left_hug=False
self.right_veer=True
In the main driving loop, you will also need to define what the EV3 needs to do for each flag that's active.
if self.left_hug:
for m,p in zip(self.motors,self.steer(course,total_power)):
m.run_direct(duty_cycle_sp=p)
elif self.right_hug:
for m,p in zip(self.motors,self.steer(-course,total_power+5)):
m.run_direct(duty_cycle_sp=p)
elif self.right_veer:
while self.wall_motor.position>-370:
self.wall_motor.on_to_position(speed=30,position=-420,brake=True,block=True)
self.wall_motor.off()
for m,p in zip(self.motors,self.steer(35,total_power)):
m.run_direct(duty_cycle_sp=p)
sleep(0.4)
self.right_veer=False
self.straight_to_right=True
elif self.straight_to_right:
if self.ir.proximity>self.target:
for m,p in zip(self.motors,self.steer(0,total_power+5)):
m.run_direct(duty_cycle_sp=p)
else:
self.straight_to_right=False
self.right_hug=True
elif self.left_veer:
while self.wall_motor.position<-70.0:
self.wall_motor.on_to_position(speed=30,position=0,brake=True,block=True)
self.wall_motor.off()
for m,p in zip(self.motors,self.steer(-35,total_power)):
m.run_direct(duty_cycle_sp=p)
sleep(0.45)
self.left_veer=False
self.straight_to_left=True
elif self.straight_to_left:
if self.ir.proximity>self.target:
for m,p in zip(self.motors,self.steer(0,total_power)):
m.run_direct(duty_cycle_sp=p)
else:
self.sraight_to_left=False
self.left_hug=True
When self.right_veer is active, for example, the medium motor turns the turntable (and thus the wall sensor), and the driving motors are set to turn for some arbitrary value (a course of 35 in this instance). After this is done, the flag will deactivate and the next flag activates and so on.
elif self.right_veer:
self.wall_motor.on_to_position(speed=30,position=-420,brake=True,block=True)
self.wall_motor.off()
for m,p in zip(self.motors,self.steer(35,total_power)):
m.run_direct(duty_cycle_sp=p)
sleep(0.4)
self.right_veer=False
self.straight_to_right=True
Opponent configurations
Figuring out the win condition on the opponent's end took quite a few revisions, many of them made in response to limitations encountered with the sensors.
For example, early in the development phase, I wanted to have the front-facing ir-sensor (in place of the touch sensor) handling both lane changing and also triggering the win condition. The EV3 would perform a lane change once within a certain distance and attempt an overtake. Winning consisted of the sensor no-longer picking up the beacon signal (as it would've passed the opponent car by then).
In practice however, the "cone of visibility" for the sensor looking up the beacon is quite limited. Initial tests involved pushing the sensor far in front. Otherwise, the beacon would've been out of visibility before the EV3 had barely caught up with the opponent.
An alternate solution was to use the side-mounted color sensor to detect a color flag on the opponent vehicle. Such was the case in the video above showing the lane-changing feature. However, for the color sensor to pick up a color properly, it needs to be quite close to the color flag. Although real motorsport racing often sees cars beside each other with only inches apart, the drivers also have much finer control over their driving line. Getting the EV3 and the opponent car to be that close together requires a lot of planning on the track-building process. Having to micro-manage the track along with balancing the vehicle speeds to get the right conditions makes the game a lot less accessible than it should be.
After changing the win condition to simply bumping the opponent, this was no longer a valid concern.
Beyond Math Racer
So what's next? Well, as you saw in the introduction video, when the game is won, the timer still moves until it reaches the expiration point. It would be good to get that fixed.
A restart function was being tested, but I have to figure out how best to keep the session alive while waiting for the EV3 to return to the start line. It's possible to use the BG music trick. In that case, an EV3 event would need to interrupt the Alexa response, but doing so introduced some minor issues I need a bit of time to work through.
On a bigger-picture scale, I want to re-explore the driving of the EV3. This may involve looking further into the lane-changing feature, or more likely, use a cmucam like a pixy2 to get smoother driving. I definitely wanted to stick with stock components for this project as I don't have a pixy2 and didn't want to delve into the coding required for that.
Beyond all that, I could use a good night's rest. Plus the kids have been asking me to build them a robot they can actually play with (the older one is just learning to count, so maybe Math Racer will have to come later), so there's that. :)
Comments