diff --git a/python_scripts/README.md b/python_scripts/README.md new file mode 100644 index 0000000..e2c8b38 --- /dev/null +++ b/python_scripts/README.md @@ -0,0 +1,39 @@ +# race_in_circles + +The `race_in_circles.py` script allows you to automate racing circular tracks using pyautogui and PS Remote Play. NOTE: The track MUST be circular. I think the script should work on any platform. + +## Instructions + +All you need to do is open PS Remote Play and then navigate to the race start screen for the track you want to automate. This is the screen with `Start`, `Settings`, and `Exit` buttons. Make sure that the remote play window is in the middle of your primary monitor because the script clicks the center of the screen to get the winow's focus. + +Next, open a terminal with administrator privileges (I had to use administrator privileges, but you might not have to). Navigate to the directory containing this script and run it: + +```bash +py race_in_circles.py +``` + +Voila! You are now racing in circles. + +By default this will race around a circular track with left turns for 200 seconds before navigating through the menus to restart the race. You can change the behavior by passing some command line arguments. + +- To change the side of the track you hug, you can pass the flag `--direction=DIRECTION`. By default you hug the `right` rail. You can also simply pass `-left` or `-l` or `-right` or `-r`. +- You can change the duration with `--duration=DURATION`, where `DURATION` is the number of seconds that the race takes. You can also simply pass a number, like `200`. To get a sense for how long the race takes, start the script with a long duration, say `1000` seconds, and see how long it takes to navigate the course and then restart the script using the new duration (you should add some padding to account for collisions, maybe +10 seconds to be safe). + +The following command will automate a 120 second race where you hug the left hand rail of the course: + +```bash +py race_in_circles.py --direction=left --duration=120 +``` + +Another command that does the same thing: + +```bash +py race_in_circles.py -left 120 +``` + +## Notes + +Right now the script simply navigates through the menus for standard races and runs a race based on timing, meaning if you spin out or something the timing will likely be off. + +If you want to automate a different event, say a championship series, you can edit the script with new menu navigation commands. These are self explanatory in the script. + diff --git a/python_scripts/race_in_circles.py b/python_scripts/race_in_circles.py new file mode 100644 index 0000000..6d765eb --- /dev/null +++ b/python_scripts/race_in_circles.py @@ -0,0 +1,137 @@ +import sys +import time +import random +import pyautogui + +# Default parameters - these can be overridden with command +# line arguments. See end of script for details. +RACE_DURATION = 200 +DIRECTION = "right" +SILENCE = False + + +def press(key: str) -> None: + """Press a key. + + Using vanilla `pyautogui.press()` will not register the keystroke + because GT requires you hold a keypress for a small duration.""" + with pyautogui.hold(key): + time.sleep(0.2) + + +def hold(key: str, duration: float) -> None: + """Hold a key for some duration.""" + + with pyautogui.hold(key): + time.sleep(duration) + + +def ride_rail(direction: str) -> None: + """ + This will drive a car around any convex hull while hugging + the `direction` rail. If you pass `left`, your car will hug + the left rail, thus allowing you to go around righthand turns. + """ + + race_start = time.time() + with pyautogui.hold("up"): + while time.time() - race_start < RACE_DURATION: + hold(direction, (random.randrange(200) / 1000)) + time.sleep((random.randrange(100) / 100)) + + +def race(direction: str) -> None: + """`direction` is the rail to hug, not the direction to turn!""" + + ride_rail(direction) + + +def end_race() -> None: + """Navigate through post-race menus and replay.""" + + commands = [ + "enter", + "enter", + "enter", + "enter", + "enter", + "enter", + "enter", + "right", + "enter", + "down", + "down", + "down", + "left", + "left", + "enter", + ] + for command in commands: + press(command) + time.sleep((random.randrange(500) / 1000) + 2) + + +def start_race(first: bool) -> None: + """Initiate race from the start race menu.""" + if first: + # Click center of screen to focus remote play window. + # You can reposition and resize remote play window, just + # make sure you update where you click. I don't know how to + # use pyautogui in headless mode. + width, height = pyautogui.size() + center = width / 2, height / 2 + pyautogui.moveTo(center) + pyautogui.click() + time.sleep(1) + + # This is the button sequence you press when the 'replay' + # button IS NOT visible on the race start screen. + press("down") + press("down") + press("down") + press("left") + press("enter") + else: + # This is the button sequence you press when the 'replay' + # button IS visible on the race start screen. + press("down") + press("down") + press("down") + press("left") + press("left") + press("enter") + + +if __name__ == "__main__": + + args = sys.argv + for arg in args[1:]: + if "=" in arg: + flag, value = arg.split("=") + if flag == "--direction": + DIRECTION = value + if flag == "--duration" and value.isdigit(): + RACE_DURATION = int(value) + else: + if arg == "-left" or arg == "-l": + DIRECTION = "left" + if arg == "-right" or arg == "-r": + DIRECTION = "right" + if arg == "-silence": + SILENCE = True + if arg.isdigit(): + RACE_DURATION = int(value) + + first = True + while True: + start = time.time() + start_race(first) + first = False + race(DIRECTION) + end_race() + end = time.time() + duration = end - start + print(duration, flush=True) + + if not SILENCE: + print(f"{(((60*60) / duration)):.2f} races/hour", flush=True)