Skip to content

Commit e4706b4

Browse files
DomNomNomtarehart
authored andcommitted
Show how to use rlbottraining (#21)
1 parent b40b781 commit e4706b4

6 files changed

Lines changed: 201 additions & 0 deletions

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Include everything the framework requires
22
# You will automatically get updates for all versions starting with "1.".
33
rlbot==1.*
4+
rlbottraining
45

56
# This will cause pip to auto-upgrade and stop scaring people with warning messages
67
pip

training/drive_to_ball_grader.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from dataclasses import dataclass, field
2+
from math import sqrt
3+
from typing import Optional
4+
5+
from rlbot.training.training import Grade, Pass, Fail
6+
7+
from rlbottraining.grading.training_tick_packet import TrainingTickPacket
8+
from rlbottraining.common_graders.timeout import FailOnTimeout
9+
from rlbottraining.common_graders.compound_grader import CompoundGrader
10+
from rlbottraining.grading.grader import Grader
11+
12+
13+
"""
14+
This file shows how to create Graders which specify when the Exercises finish
15+
and whether the bots passed the exercise.
16+
"""
17+
18+
19+
class DriveToBallGrader(CompoundGrader):
20+
"""
21+
Checks that the car gets to the ball in a reasonable amount of time.
22+
"""
23+
def __init__(self, timeout_seconds=4.0, min_dist_to_pass=200):
24+
super().__init__([
25+
PassOnNearBall(min_dist_to_pass=min_dist_to_pass),
26+
FailOnTimeout(timeout_seconds),
27+
])
28+
29+
@dataclass
30+
class PassOnNearBall(Grader):
31+
"""
32+
Returns a Pass grade once the car is sufficiently close to the ball.
33+
"""
34+
35+
min_dist_to_pass: float = 200
36+
car_index: int = 0
37+
38+
def on_tick(self, tick: TrainingTickPacket) -> Optional[Grade]:
39+
car = tick.game_tick_packet.game_cars[self.car_index].physics.location
40+
ball = tick.game_tick_packet.game_ball.physics.location
41+
42+
dist = sqrt(
43+
(car.x - ball.x) ** 2 +
44+
(car.y - ball.y) ** 2
45+
)
46+
if dist <= self.min_dist_to_pass:
47+
return Pass()
48+
return None

training/example_playlist.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import hello_world_training
2+
import rlbottraining.common_exercises.bronze_goalie as bronze_goalie
3+
4+
def make_default_playlist():
5+
exercises = (
6+
hello_world_training.make_default_playlist() +
7+
bronze_goalie.make_default_playlist()
8+
)
9+
for exercise in exercises:
10+
exercise.match_config = hello_world_training.make_match_config_with_my_bot()
11+
12+
return exercises

training/hello_world_training.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from pathlib import Path
2+
from dataclasses import dataclass, field
3+
from math import pi
4+
5+
from rlbot.utils.game_state_util import GameState, BoostState, BallState, CarState, Physics, Vector3, Rotator
6+
from rlbot.matchconfig.match_config import MatchConfig, PlayerConfig, Team
7+
from rlbottraining.common_exercises.common_base_exercises import StrikerExercise
8+
from rlbottraining.rng import SeededRandomNumberGenerator
9+
from rlbottraining.match_configs import make_empty_match_config
10+
from rlbottraining.grading.grader import Grader
11+
from rlbottraining.training_exercise import TrainingExercise, Playlist
12+
13+
import training_util
14+
from drive_to_ball_grader import DriveToBallGrader
15+
16+
17+
def make_match_config_with_my_bot() -> MatchConfig:
18+
# Makes a config which only has our bot in it for now.
19+
# For more defails: https://youtu.be/uGFmOZCpel8?t=375
20+
match_config = make_empty_match_config()
21+
match_config.player_configs = [
22+
PlayerConfig.bot_config(
23+
Path(__file__).absolute().parent.parent / 'python_example' / 'python_example.cfg',
24+
Team.BLUE
25+
),
26+
]
27+
return match_config
28+
29+
@dataclass
30+
class StrikerPatience(StrikerExercise):
31+
"""
32+
Drops the ball from a certain height, requiring the bot to not drive
33+
underneath the ball until it's in reach.
34+
"""
35+
36+
car_start_x: float = 0
37+
38+
def make_game_state(self, rng: SeededRandomNumberGenerator) -> GameState:
39+
return GameState(
40+
ball=BallState(physics=Physics(
41+
location=Vector3(0, 4400, 1000),
42+
velocity=Vector3(0, 0, 200),
43+
angular_velocity=Vector3(0, 0, 0))),
44+
cars={
45+
0: CarState(
46+
physics=Physics(
47+
location=Vector3(self.car_start_x, 3000, 0),
48+
rotation=Rotator(0, pi / 2, 0),
49+
velocity=Vector3(0, 0, 0),
50+
angular_velocity=Vector3(0, 0, 0)),
51+
jumped=False,
52+
double_jumped=False,
53+
boost_amount=0)
54+
},
55+
boosts={i: BoostState(0) for i in range(34)},
56+
)
57+
58+
@dataclass
59+
class DrivesToBallExercise(TrainingExercise):
60+
"""
61+
Checks that we drive to the ball when it's in the center of the field.
62+
"""
63+
grader: Grader = field(default_factory=DriveToBallGrader)
64+
65+
def make_game_state(self, rng: SeededRandomNumberGenerator) -> GameState:
66+
return GameState(
67+
ball=BallState(physics=Physics(
68+
location=Vector3(0, 0, 100),
69+
velocity=Vector3(0, 0, 0),
70+
angular_velocity=Vector3(0, 0, 0))),
71+
cars={
72+
0: CarState(
73+
physics=Physics(
74+
location=Vector3(0, 2000, 0),
75+
rotation=Rotator(0, -pi / 2, 0),
76+
velocity=Vector3(0, 0, 0),
77+
angular_velocity=Vector3(0, 0, 0)),
78+
jumped=False,
79+
double_jumped=False,
80+
boost_amount=100)
81+
},
82+
boosts={i: BoostState(0) for i in range(34)},
83+
)
84+
85+
86+
def make_default_playlist() -> Playlist:
87+
exercises = [
88+
StrikerPatience('start perfectly center'),
89+
StrikerPatience('start on the right', car_start_x=-1000),
90+
DrivesToBallExercise('Get close to ball'),
91+
DrivesToBallExercise('Get close-ish to ball', grader=DriveToBallGrader(min_dist_to_pass=1000))
92+
]
93+
for exercise in exercises:
94+
exercise.match_config = make_match_config_with_my_bot()
95+
96+
return exercises

training/training_util.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from rlbottraining.rng import SeededRandomNumberGenerator
2+
3+
from rlbot.utils.game_state_util import Vector3
4+
5+
6+
def get_car_start_near_goal(rng: SeededRandomNumberGenerator) -> Vector3:
7+
return Vector3(rng.uniform(1000, 2000), 3000, 0)

training/unit_tests.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import unittest
2+
3+
from rlbot.training.training import Pass, Fail
4+
from rlbottraining.exercise_runner import run_playlist
5+
6+
from hello_world_training import StrikerPatience
7+
8+
class PatienceTest(unittest.TestCase):
9+
"""
10+
These units check that this bot behaves as we expect,
11+
with regards to the StrikerPatience exercise.
12+
13+
By default, the bot isn't very smart so it'll fail in the cases where
14+
patience is required but passes in cases where no patience is required.
15+
16+
Tutorial:
17+
https://youtu.be/hCw250aGN8c?list=PL6LKXu1RlPdxh9vxmG1y2sghQwK47_gCH&t=187
18+
"""
19+
20+
def test_patience_required(self):
21+
result_iter = run_playlist([StrikerPatience(name='patience required')])
22+
results = list(result_iter)
23+
self.assertEqual(len(results), 1)
24+
result = results[0]
25+
self.assertEqual(result.exercise.name, 'patience required')
26+
self.assertIsInstance(result.grade, Fail) # If you make the bot is smarter, update this assert that we pass.
27+
28+
def test_no_patience_required(self):
29+
result_iter = run_playlist([StrikerPatience(name='no patience required', car_start_x=-1000)])
30+
results = list(result_iter)
31+
self.assertEqual(len(results), 1)
32+
result = results[0]
33+
self.assertEqual(result.exercise.name, 'no patience required')
34+
self.assertIsInstance(result.grade, Pass)
35+
36+
if __name__ == '__main__':
37+
unittest.main()

0 commit comments

Comments
 (0)