From d6e4dd8110c36f1b6d175041778563f2bd3af734 Mon Sep 17 00:00:00 2001 From: Tom Casavant Date: Sun, 5 May 2024 11:20:06 -0400 Subject: [PATCH 1/3] Initial pep8 fixes --- bot.py | 223 +++++++++++++++++++++++++++++++++++++-------------------- gb.py | 170 ++++++++++++++++++++++++++++--------------- 2 files changed, 257 insertions(+), 136 deletions(-) diff --git a/bot.py b/bot.py index 8631e14..285dcde 100644 --- a/bot.py +++ b/bot.py @@ -5,40 +5,43 @@ import toml import os + class Bot: def __init__(self, config_path="config.toml"): - # If config_path is not provided, use the config.toml file in the same directory as the script - self.script_dir = os.path.dirname(os.path.realpath(__file__)) # Get the directory of the current script + # If config_path is not provided, use the config.toml file in the same + # directory as the script + # Get the directory of the current script + self.script_dir = os.path.dirname(os.path.realpath(__file__)) config_path = os.path.join(self.script_dir, "config.toml") - with open(config_path, 'r') as config_file: + with open(config_path, "r") as config_file: self.config = toml.load(config_file) - self.mastodon_config = self.config.get('mastodon', {}) - self.gameboy_config = self.config.get('gameboy', {}) + self.mastodon_config = self.config.get("mastodon", {}) + self.gameboy_config = self.config.get("gameboy", {}) self.mastodon = self.login() - print(self.gameboy_config.get('rom')) - rom = os.path.join(self.script_dir, self.gameboy_config.get('rom')) + print(self.gameboy_config.get("rom")) + rom = os.path.join(self.script_dir, self.gameboy_config.get("rom")) self.gameboy = Gameboy(rom, True) def simulate(self): while True: - #print(self.gameboy.is_running()) + # print(self.gameboy.is_running()) if True: - #self.gameboy.random_button() + # self.gameboy.random_button() buttons = { - "a" : self.gameboy.a, - "b" : self.gameboy.b, - "start" : self.gameboy.start, - "select" : self.gameboy.select, - "up" : self.gameboy.dpad_up, - "down" : self.gameboy.dpad_down, - "right" : self.gameboy.dpad_right, - "left" : self.gameboy.dpad_left, - "random" : self.gameboy.random_button, - "tick" : "tick" + "a": self.gameboy.a, + "b": self.gameboy.b, + "start": self.gameboy.start, + "select": self.gameboy.select, + "up": self.gameboy.dpad_up, + "down": self.gameboy.dpad_down, + "right": self.gameboy.dpad_right, + "left": self.gameboy.dpad_left, + "random": self.gameboy.random_button, + "tick": "tick", } - #self.gameboy.random_button() + # self.gameboy.random_button() print(buttons) press = input("Button: ") if press == "tick": @@ -47,18 +50,18 @@ def simulate(self): else: buttons[press]() self.gameboy.pyboy.tick() - #time.sleep(1) + # time.sleep(1) def random_button(self): buttons = { - "a" : self.gameboy.a, - "b" : self.gameboy.b, - "start" : self.gameboy.start, - "select" : self.gameboy.select, - "up" : self.gameboy.dpad_up, - "down" : self.gameboy.dpad_down, - "right" : self.gameboy.dpad_right, - "left" : self.gameboy.dpad_left + "a": self.gameboy.a, + "b": self.gameboy.b, + "start": self.gameboy.start, + "select": self.gameboy.select, + "up": self.gameboy.dpad_up, + "down": self.gameboy.dpad_down, + "right": self.gameboy.dpad_right, + "left": self.gameboy.dpad_left, } random_button = random.choice(list(buttons.keys())) @@ -66,30 +69,37 @@ def random_button(self): action() return random_button - def login(self): - server = self.mastodon_config.get('server') + server = self.mastodon_config.get("server") print(f"Logging into {server}") - return Mastodon(access_token=self.mastodon_config.get('access_token'), api_base_url=server) + return Mastodon( + access_token=self.mastodon_config.get("access_token"), api_base_url=server + ) - def post_poll(self, status, options, expires_in=60*60, reply_id=None): - poll = self.mastodon.make_poll(options, expires_in=expires_in, hide_totals=False) - return self.mastodon.status_post(status, in_reply_to_id=reply_id, language='en', poll=poll) + def post_poll(self, status, options, expires_in=60 * 60, reply_id=None): + poll = self.mastodon.make_poll( + options, expires_in=expires_in, hide_totals=False + ) + return self.mastodon.status_post( + status, in_reply_to_id=reply_id, language="en", poll=poll + ) def save_ids(self, post_id, poll_id): - script_dir = os.path.dirname(os.path.realpath(__file__)) # Get the directory of the current script + # Get the directory of the current script + script_dir = os.path.dirname(os.path.realpath(__file__)) ids_loc = os.path.join(script_dir, "ids.txt") - with open(ids_loc, 'w') as file: + with open(ids_loc, "w") as file: file.write(f"{post_id},{poll_id}") def read_ids(self): try: - script_dir = os.path.dirname(os.path.realpath(__file__)) # Get the directory of the current script + # Get the directory of the current script + script_dir = os.path.dirname(os.path.realpath(__file__)) ids_loc = os.path.join(script_dir, "ids.txt") - with open(ids_loc, 'r') as file: + with open(ids_loc, "r") as file: content = file.read() if content: - post_id, poll_id = content.split(',') + post_id, poll_id = content.split(",") return post_id, poll_id except FileNotFoundError: return None, None @@ -111,7 +121,7 @@ def take_action(self, result): "🅰": self.gameboy.a, "🅱": self.gameboy.b, "start": self.gameboy.start, - "select": self.gameboy.select + "select": self.gameboy.select, } print(buttons) # Perform the corresponding action @@ -128,7 +138,7 @@ def retry_mastodon_call(self, func, retries=5, interval=10, *args, **kwargs): except Exception as e: print(f"Failure to execute {e}") time.sleep(interval) - return False # Failed to execute + return False # Failed to execute def run(self): self.gameboy.load() @@ -138,18 +148,18 @@ def run(self): if post_id: try: self.unpin_posts(post_id, poll_id) - except: + except BaseException: time.sleep(30) self.unpin_posts(post_id, poll_id) poll_status = self.mastodon.status(poll_id) - poll_results = poll_status.poll['options'] - max_result = max(poll_results, key=lambda x: x['votes_count']) - if (max_result['votes_count'] == 0): + poll_results = poll_status.poll["options"] + max_result = max(poll_results, key=lambda x: x["votes_count"]) + if max_result["votes_count"] == 0: button = self.random_button() top_result = f"Random (no votes, chose {button})" else: - top_result = max_result['title'] + top_result = max_result["title"] self.take_action(top_result) frames = self.gameboy.loop_until_stopped() @@ -161,57 +171,114 @@ def run(self): self.gameboy.empty_directory(gif_dir) image = self.gameboy.screenshot() - media = self.retry_mastodon_call(self.mastodon.media_post, retries=5, interval=10, media_file=image, description='Screenshot of Pokemon Gold') + media = self.retry_mastodon_call( + self.mastodon.media_post, + retries=5, + interval=10, + media_file=image, + description="Screenshot of Pokemon Gold", + ) media_ids = [] - try: # Probably add a check here if generating a gif is enabled (so we don't have to generate one every single hour?) + # Probably add a check here if generating a gif is enabled (so we don't + # have to generate one every single hour?) + try: previous_frames = self.gameboy.get_recent_frames("screenshots", 25) - previous_media = self.retry_mastodon_call(self.mastodon.media_post, retries=5, interval=10, media_file=previous_frames, description="Video of the previous 45 frames") - media_ids = [media['id'], previous_media['id']] - except: - media_ids = [media['id']] - #try: + previous_media = self.retry_mastodon_call( + self.mastodon.media_post, + retries=5, + interval=10, + media_file=previous_frames, + description="Video of the previous 45 frames", + ) + media_ids = [media["id"], previous_media["id"]] + except BaseException: + media_ids = [media["id"]] + # try: # media = self.mastodon.media_post(image, description='Screenshot of pokemon gold') - #except: + # except: # time.sleep(45) # media = self.mastodon.media_post(image, description='Screenshot of pokemon gold') - #time.sleep(50) - post = self.retry_mastodon_call(self.mastodon.status_post, retries=5, interval=10, status=f"Previous Action: {top_result}\n\n#pokemon #gameboy #nintendo #FediPlaysPokemon", media_ids=[media_ids]) - #try: + # time.sleep(50) + post = self.retry_mastodon_call( + self.mastodon.status_post, + retries=5, + interval=10, + status=f"Previous Action: {top_result}\n\n#pokemon #gameboy #nintendo #FediPlaysPokemon", + media_ids=[media_ids], + ) + # try: # post = self.mastodon.status_post(f"Previous Action: {top_result}\n\n#pokemon #gameboy #nintendo", media_ids=[media['id']]) - #except: + # except: # time.sleep(30) - # post = self.mastodon.status_post(f"Previous Action: {top_result}\n\n#pokemon #gamebody #nintendo", media_ids=[media['id']]) - poll = self.retry_mastodon_call(self.post_poll, retries=5, interval=10, status="Vote on the next action:\n\n#FediPlaysPokemon", options=["Up ⬆️", "Down ⬇️", "Right ➡️ ", "Left ⬅️", "🅰", "🅱", "Start", "Select"], reply_id=post['id'] ) + # post = self.mastodon.status_post(f"Previous Action: + # {top_result}\n\n#pokemon #gamebody #nintendo", + # media_ids=[media['id']]) + poll = self.retry_mastodon_call( + self.post_poll, + retries=5, + interval=10, + status="Vote on the next action:\n\n#FediPlaysPokemon", + options=[ + "Up ⬆️", + "Down ⬇️", + "Right ➡️ ", + "Left ⬅️", + "🅰", + "🅱", + "Start", + "Select", + ], + reply_id=post["id"], + ) - #ry: + # ry: # poll = self.post_poll("Vote on the next action:", ["Up ⬆️", "Down ⬇️", "Right ➡️ ", "Left ⬅️", "🅰", "🅱", "Start", "Select"], reply_id=post['id']) - #except: + # except: # time.sleep(30) # poll = self.post_poll("Vote on the next action:", ["Up ⬆️", "Down ⬇️", "Right ➡️ ", "Left ⬅️", "🅰", "🅱", "Start", "Select"], reply_id=post['id']) - self.retry_mastodon_call(self.pin_posts, retries=5, interval=10, post_id=post['id'], poll_id=poll['id']) - #try: + self.retry_mastodon_call( + self.pin_posts, + retries=5, + interval=10, + post_id=post["id"], + poll_id=poll["id"], + ) + # try: # self.pin_posts(post['id'], poll['id']) - #except: + # except: # time.sleep(30) # self.pin_posts(post['id'], poll['id']) - #result = self.gameboy.build_gif("gif_images") + # result = self.gameboy.build_gif("gif_images") result = False if result: - gif = self.retry_mastodon_call(self.mastodon.media_post, retries=5, interval=10, media_file=result, description='Video of pokemon gold movement') - self.retry_mastodon_call(self.mastodon.status_post, retries=10, interval=10, status="#Pokemon #FediPlaysPokemon", media_ids=[gif['id']], in_reply_to_id=poll['id']) + gif = self.retry_mastodon_call( + self.mastodon.media_post, + retries=5, + interval=10, + media_file=result, + description="Video of pokemon gold movement", + ) + self.retry_mastodon_call( + self.mastodon.status_post, + retries=10, + interval=10, + status="#Pokemon #FediPlaysPokemon", + media_ids=[gif["id"]], + in_reply_to_id=poll["id"], + ) - self.save_ids(post['id'], poll['id']) + self.save_ids(post["id"], poll["id"]) # Save game state self.gameboy.save() def test(self): self.gameboy.load() - self.gameboy.get_recent_frames('screenshots', 25) - #self.gameboy.build_gif("gif_images") - '''while True: + self.gameboy.get_recent_frames("screenshots", 25) + # self.gameboy.build_gif("gif_images") + """while True: inp = input("Action: ") buttons = { "up": self.gameboy.dpad_up, @@ -239,14 +306,14 @@ def test(self): self.gameboy.save() #self.gameboy.build_gif("gif_images") #self.take_action(inp) - #self.gameboy.tick(300)''' + #self.gameboy.tick(300)""" + -if __name__ == '__main__': +if __name__ == "__main__": bot = Bot() - #bot.test() + # bot.test() bot.run() # for i in range(2): # bot.run() # time.sleep(60) - #bot.simulate() - + # bot.simulate() diff --git a/gb.py b/gb.py index cbc5086..d441f9d 100644 --- a/gb.py +++ b/gb.py @@ -8,8 +8,14 @@ from moviepy.editor import ImageSequenceClip import numpy as np + class Gameboy: + """Provides an easy way to interface with pyboy + Args: + rom (str): A string pointing to a rom file (MUST be GB or GBC, no GBA files) + debug (bool, optional): Enable debug mode. Defaults to false + """ def __init__(self, rom, debug=False): self.debug = debug self.rom = rom @@ -17,27 +23,30 @@ def __init__(self, rom, debug=False): self.pyboy = self.load_rom(self.rom) self.pyboy.set_emulation_speed(0) - def start_thread(self): - self.pyboy = self.load_rom(self.rom) - self.pyboy.set_emulation_speed(1) - print(self.pyboy) - #self.run() - def is_running(self): + """ Returns True if bot is running in constant loop mode, false otherwise """ return self.running def run(self) -> None: + """ Continuously loop while pressing random buttons on the gameboy """ self.running = True while True: self.random_button() def tick(self, ticks=1, gif=True): + """ Advances the gameboy by a specified number of frames. + + Args: + ticks (int, optional): The number of frames to advance. Defaults to 1 + gif (bool, optional): Generates screenshots for the gif if True + """ for tick in range(ticks): if gif: self.screenshot("gif_images") self.pyboy.tick() def compare_frames(self, frame1, frame2): + """ Compares two frames from gameboy screenshot, returns a percentage difference between the two """ arr1 = np.array(frame1) arr2 = np.array(frame2) @@ -48,156 +57,194 @@ def compare_frames(self, frame1, frame2): return percent def get_recent_frames(self, directory, num_frames=100): + """Gets the most recent frames from a provided directory""" script_dir = os.path.dirname(os.path.realpath(__file__)) screenshot_dir = os.path.join(script_dir, directory) - image_files = [os.path.join(screenshot_dir, i) for i in os.listdir(screenshot_dir)] # Probably should replace this with heap (especially since there are so many image files) + # Probably should replace this with heap (especially since there are so + # many image files) + image_files = [ + os.path.join(screenshot_dir, i) for i in os.listdir(screenshot_dir) + ] image_files.sort(key=os.path.getmtime) latest = image_files[-(num_frames):] count = 0 for image in latest: print(image) - count+=1 - shutil.copy(image, os.path.join(script_dir, 'tmp', f"{count}.png")) + count += 1 + shutil.copy(image, os.path.join(script_dir, "tmp", f"{count}.png")) - self.build_gif(os.path.join(script_dir, 'tmp'), fps=5, output_name="test.mp4") - self.empty_directory(os.path.join(script_dir, 'tmp')) - return os.path.join(script_dir, 'test.mp4') + self.build_gif(os.path.join(script_dir, "tmp"), fps=5, output_name="test.mp4") + self.empty_directory(os.path.join(script_dir, "tmp")) + return os.path.join(script_dir, "test.mp4") def empty_directory(self, directory): - image_files = [i for i in os.listdir(directory) if os.path.isfile(os.path.join(directory, i))] + """ Deletes all images in the provided directory """ + image_files = [ + i + for i in os.listdir(directory) + if os.path.isfile(os.path.join(directory, i)) + ] for img in image_files: os.remove(os.path.join(directory, img)) def build_gif(self, image_path, delete=True, fps=120, output_name="action.mp4"): - script_dir = os.path.dirname(os.path.realpath(__file__)) # Get the directory of the current script + """ Build a gif from a folder of images """ + # Get the directory of the current script + script_dir = os.path.dirname(os.path.realpath(__file__)) gif_dir = os.path.join(script_dir, image_path) - image_files = [i for i in os.listdir(gif_dir) if os.path.isfile(os.path.join(gif_dir, i))] - #image_files.sort() - image_files.sort(key=lambda x: int(''.join(filter(str.isdigit, x)))) + image_files = [ + i for i in os.listdir(gif_dir) if os.path.isfile(os.path.join(gif_dir, i)) + ] + # image_files.sort() + image_files.sort(key=lambda x: int("".join(filter(str.isdigit, x)))) images = [] print(len(image_files)) - #image1 = Image.open(os.path.join(gif_dir, image_files[30])).convert('L') - #diffs = [] for file in image_files: - #diffs.append(self.compare_frames(image1, Image.open(os.path.join(gif_dir, file)).convert('L'))) - - gameboy_outline = Image.open(os.path.join(script_dir, 'gameboy.png')).convert("RGB") + + gameboy_outline = Image.open( + os.path.join(script_dir, "gameboy.png") + ).convert("RGB") img = Image.open(os.path.join(gif_dir, file)).convert("RGB") img = img.resize((181, 163)) combined = gameboy_outline.copy() combined.paste(img, (165, 151)) combined.save(os.path.join(gif_dir, file)) images.append(os.path.join(gif_dir, file)) - #with Image.open(os.path.join(gif_dir, file)) as img: - # images.append(img.copy()) - #if delete: - # os.remove(os.path.join(gif_dir, file)) if images: save_path = None - #diffs = diffs[30:] - #if max(diffs) > 10: - duration = int(1000/fps) - #freeze_frames = [images[-1]] * int(1000/duration) # Freeze on last frame - # print(len(freeze_frames)) - #print(f"Duration {duration}") - #frames = [images[0]]*350 + images + duration = int(1000 / fps) frames = images save_path = os.path.join(script_dir, output_name) clip = ImageSequenceClip(frames, fps=fps) - clip.write_videofile(save_path, codec='libx264') + clip.write_videofile(save_path, codec="libx264") if delete: for img in images: os.remove(img) - #images[0].save(save_path, save_all=True, append_images=images[1:]+freeze_frames, interlace=False, loop=0, duration=duration, disposal=1) return save_path return False def stop(self): + """ Stops the continuous gameboy loop """ self.running = False def load_rom(self, rom): - return PyBoy(rom, window_type="SDL2" if self.debug else "headless", window_scale=3, debug=False, game_wrapper=True) + """ Loads the rom into a pyboy object """ + return PyBoy( + rom, + window_type="SDL2" if self.debug else "headless", + window_scale=3, + debug=False, + game_wrapper=True, + ) def dpad_up(self) -> None: + """ Presses up on the gameboy """ self.pyboy.send_input(WindowEvent.PRESS_ARROW_UP) self.tick(4) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_UP) - #print(self.pyboy.stop()) - #self.tick() def dpad_down(self) -> None: + """ Presses down on the gameboy """ self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) print("down") self.tick(3) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN) - #self.tick() + # self.tick() def dpad_right(self) -> None: + """ Presses right on the gameboy """ self.pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT) print("right") self.tick(3) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_RIGHT) - #self.tick() + # self.tick() def dpad_left(self) -> None: + """ Presses left on the gameboy """ self.pyboy.send_input(WindowEvent.PRESS_ARROW_LEFT) print("left") self.tick(3) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_LEFT) - #self.tick() + # self.tick() def a(self) -> None: + """ Presses a on the gameboy """ self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) print("a") self.tick(3) self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) - #self.tick() + # self.tick() def b(self) -> None: + """ Presses b on the gameboy """ self.pyboy.send_input(WindowEvent.PRESS_BUTTON_B) print("b") self.tick(3) self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_B) - #self.tick() + # self.tick() def start(self) -> None: + """ Presses start on the gameboy """ self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) print("start") self.tick(3) self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) - #self.tick() + # self.tick() def select(self) -> None: + """ Presses select on the gameboy """ self.pyboy.send_input(WindowEvent.PRESS_BUTTON_SELECT) print("select") self.tick(3) self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_SELECT) - def screenshot(self, path='screenshots'): - script_dir = os.path.dirname(os.path.realpath(__file__)) # Get the directory of the current script + def screenshot(self, path="screenshots"): + """ Takes a screenshot of gameboy screen and saves it to the path """ + # Get the directory of the current script + script_dir = os.path.dirname(os.path.realpath(__file__)) screenshot_dir = os.path.join(script_dir, path) - os.makedirs(screenshot_dir, exist_ok=True) # Create screenshots directory if it doesn't exist + # Create screenshots directory if it doesn't exist + os.makedirs(screenshot_dir, exist_ok=True) # Get existing screenshot numbers - screenshot_numbers = [int(re.search(r'screenshot_(\d+)\.png', filename).group(1)) for filename in os.listdir(screenshot_dir) if re.match(r'screenshot_\d+\.png', filename)] + screenshot_numbers = [ + int(re.search(r"screenshot_(\d+)\.png", filename).group(1)) + for filename in os.listdir(screenshot_dir) + if re.match(r"screenshot_\d+\.png", filename) + ] next_number = max(screenshot_numbers, default=0) + 1 # Save the screenshot with the next available number - screenshot_path = os.path.join(screenshot_dir, f'screenshot_{next_number}.png') - screenshot_path_full = os.path.join(script_dir, 'screenshot.png') + screenshot_path = os.path.join(screenshot_dir, f"screenshot_{next_number}.png") + screenshot_path_full = os.path.join(script_dir, "screenshot.png") self.pyboy.screen_image().save(screenshot_path_full) - shutil.copyfile(screenshot_path_full, screenshot_path) # Copy the screenshot to the screenshots directory + # Copy the screenshot to the screenshots directory + shutil.copyfile(screenshot_path_full, screenshot_path) return screenshot_path_full def random_button(self): - button = random.choice([self.dpad_up, self.dpad_down, self.dpad_right, self.dpad_left, self.a, self.b, self.start, self.select]) + """ Picks a random button and presses it on the gameboy """ + button = random.choice( + [ + self.dpad_up, + self.dpad_down, + self.dpad_right, + self.dpad_left, + self.a, + self.b, + self.start, + self.select, + ] + ) button() def load(self): - script_dir = os.path.dirname(os.path.realpath(__file__)) # Get the directory of the current script + """ Loads the save state """ + # Get the directory of the current script + script_dir = os.path.dirname(os.path.realpath(__file__)) save_loc = os.path.join(script_dir, "save.state") if os.path.exists(save_loc): with open(save_loc, "rb") as file: @@ -208,13 +255,16 @@ def load(self): return False def save(self): - script_dir = os.path.dirname(os.path.realpath(__file__)) # Get the directory of the current script + """ Saves current state to a file """ + # Get the directory of the current script + script_dir = os.path.dirname(os.path.realpath(__file__)) save_loc = os.path.join(script_dir, "save.state") with open(save_loc, "wb") as file: self.pyboy.save_state(file) def loop_until_stopped(self, threshold=1): + """ Simulates the gameboy bot """ script_dir = os.path.dirname(os.path.realpath(__file__)) running = True previous_frame = None @@ -224,18 +274,22 @@ def loop_until_stopped(self, threshold=1): while running: previous_frame = current_frame self.tick(30) - count+=5 - current_frame = Image.open(os.path.join(script_dir, "screenshot.png")).convert('L') + count += 5 + current_frame = Image.open( + os.path.join(script_dir, "screenshot.png") + ).convert("L") if previous_frame: diff = self.compare_frames(previous_frame, current_frame) print(f"Frame {count}: {diff}") - if (diff < threshold): + if diff < threshold: no_movement += 1 else: no_movement = 0 if no_movement > 3: running = False - if count > 1000: # Shouldn't have lasted this long, something has gone wrong + if ( + count > 1000 + ): # Shouldn't have lasted this long, something has gone wrong print("Error") return 0 return count From 9d55680079a2791a3fab8b95d47236f466de16e8 Mon Sep 17 00:00:00 2001 From: Tom Casavant Date: Sun, 5 May 2024 11:22:43 -0400 Subject: [PATCH 2/3] Fix import ordering --- bot.py | 8 +++++--- gb.py | 11 ++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bot.py b/bot.py index 285dcde..d2b9dbe 100644 --- a/bot.py +++ b/bot.py @@ -1,9 +1,11 @@ -from gb import Gameboy +import os import random import time -from mastodon import Mastodon + import toml -import os +from mastodon import Mastodon + +from gb import Gameboy class Bot: diff --git a/gb.py b/gb.py index d441f9d..451ed3d 100644 --- a/gb.py +++ b/gb.py @@ -1,12 +1,13 @@ -from pyboy import PyBoy, WindowEvent -import random -import threading import os +import random import re import shutil -from PIL import Image, ImageDraw -from moviepy.editor import ImageSequenceClip +import threading + import numpy as np +from moviepy.editor import ImageSequenceClip +from PIL import Image, ImageDraw +from pyboy import PyBoy, WindowEvent class Gameboy: From aba50e0004824e7972a6414fa7cab213dea7c456 Mon Sep 17 00:00:00 2001 From: Tom Casavant Date: Sun, 5 May 2024 11:56:03 -0400 Subject: [PATCH 3/3] More fixes for pep8 standards --- bot.py | 125 +++++++++++++++++++++++++++++---------------------------- gb.py | 60 ++++++++++++++------------- 2 files changed, 97 insertions(+), 88 deletions(-) diff --git a/bot.py b/bot.py index d2b9dbe..8048c0d 100644 --- a/bot.py +++ b/bot.py @@ -1,14 +1,22 @@ +""" + A bot that interacts with a Mastodon compatible API, plays gameboy games via a Poll +""" + import os import random import time import toml from mastodon import Mastodon +from requests.exceptions import RequestException from gb import Gameboy class Bot: + """ + A Mastodon-API Compatible bot that handles gameboy gameplay through polls + """ def __init__(self, config_path="config.toml"): # If config_path is not provided, use the config.toml file in the same @@ -16,7 +24,7 @@ def __init__(self, config_path="config.toml"): # Get the directory of the current script self.script_dir = os.path.dirname(os.path.realpath(__file__)) config_path = os.path.join(self.script_dir, "config.toml") - with open(config_path, "r") as config_file: + with open(config_path, "r", encoding="utf-8") as config_file: self.config = toml.load(config_file) self.mastodon_config = self.config.get("mastodon", {}) self.gameboy_config = self.config.get("gameboy", {}) @@ -27,34 +35,35 @@ def __init__(self, config_path="config.toml"): self.gameboy = Gameboy(rom, True) def simulate(self): + """Simulates gameboy actions by pressing random buttons, useful for testing""" while True: # print(self.gameboy.is_running()) - if True: - # self.gameboy.random_button() - buttons = { - "a": self.gameboy.a, - "b": self.gameboy.b, - "start": self.gameboy.start, - "select": self.gameboy.select, - "up": self.gameboy.dpad_up, - "down": self.gameboy.dpad_down, - "right": self.gameboy.dpad_right, - "left": self.gameboy.dpad_left, - "random": self.gameboy.random_button, - "tick": "tick", - } - # self.gameboy.random_button() - print(buttons) - press = input("Button: ") - if press == "tick": - for i in range(60): - self.gameboy.pyboy.tick() - else: - buttons[press]() + # self.gameboy.random_button() + buttons = { + "a": self.gameboy.a, + "b": self.gameboy.b, + "start": self.gameboy.start, + "select": self.gameboy.select, + "up": self.gameboy.dpad_up, + "down": self.gameboy.dpad_down, + "right": self.gameboy.dpad_right, + "left": self.gameboy.dpad_left, + "random": self.gameboy.random_button, + "tick": "tick", + } + # self.gameboy.random_button() + print(buttons) + press = input("Button: ") + if press == "tick": + for _ in range(60): self.gameboy.pyboy.tick() - # time.sleep(1) + else: + buttons[press]() + self.gameboy.pyboy.tick() + # time.sleep(1) def random_button(self): + """Chooses a random button and presses it on the gameboy""" buttons = { "a": self.gameboy.a, "b": self.gameboy.b, @@ -72,6 +81,7 @@ def random_button(self): return random_button def login(self): + """Logs into the mastodon server using config credentials""" server = self.mastodon_config.get("server") print(f"Logging into {server}") return Mastodon( @@ -79,6 +89,7 @@ def login(self): ) def post_poll(self, status, options, expires_in=60 * 60, reply_id=None): + """Posts a poll to Mastodon compatible server""" poll = self.mastodon.make_poll( options, expires_in=expires_in, hide_totals=False ) @@ -87,18 +98,20 @@ def post_poll(self, status, options, expires_in=60 * 60, reply_id=None): ) def save_ids(self, post_id, poll_id): + """Saves post IDs to a text file""" # Get the directory of the current script script_dir = os.path.dirname(os.path.realpath(__file__)) ids_loc = os.path.join(script_dir, "ids.txt") - with open(ids_loc, "w") as file: + with open(ids_loc, "w", encoding="utf-8") as file: file.write(f"{post_id},{poll_id}") def read_ids(self): + """Reads IDs from the text file""" try: # Get the directory of the current script script_dir = os.path.dirname(os.path.realpath(__file__)) ids_loc = os.path.join(script_dir, "ids.txt") - with open(ids_loc, "r") as file: + with open(ids_loc, "r", encoding="utf-8") as file: content = file.read() if content: post_id, poll_id = content.split(",") @@ -106,15 +119,20 @@ def read_ids(self): except FileNotFoundError: return None, None + return None + def pin_posts(self, post_id, poll_id): + """Pin posts to profile""" self.mastodon.status_pin(poll_id) self.mastodon.status_pin(post_id) def unpin_posts(self, post_id, poll_id): + """Unpin posts from profile""" self.mastodon.status_unpin(post_id) self.mastodon.status_unpin(poll_id) def take_action(self, result): + """Presses button on gameboy based on poll result""" buttons = { "up ⬆️": self.gameboy.dpad_up, "down ⬇️": self.gameboy.dpad_down, @@ -133,16 +151,20 @@ def take_action(self, result): else: print(f"No action defined for '{result}'.") - def retry_mastodon_call(self, func, retries=5, interval=10, *args, **kwargs): + def retry_mastodon_call(self, func, *args, retries=5, interval=10, **kwargs): + """Continuously retries mastodon call, useful for servers with timeout issues""" for _ in range(retries): try: return func(*args, **kwargs) - except Exception as e: - print(f"Failure to execute {e}") + except RequestException as e: + print(f"Failure to execute {func.__name__}: {e}") time.sleep(interval) return False # Failed to execute def run(self): + """ + Runs the main gameplay, reads mastodon poll result, takes action, generates new posts + """ self.gameboy.load() post_id, poll_id = self.read_ids() top_result = None @@ -195,26 +217,18 @@ def run(self): media_ids = [media["id"], previous_media["id"]] except BaseException: media_ids = [media["id"]] - # try: - # media = self.mastodon.media_post(image, description='Screenshot of pokemon gold') - # except: - # time.sleep(45) - # media = self.mastodon.media_post(image, description='Screenshot of pokemon gold') - # time.sleep(50) + post = self.retry_mastodon_call( self.mastodon.status_post, retries=5, interval=10, - status=f"Previous Action: {top_result}\n\n#pokemon #gameboy #nintendo #FediPlaysPokemon", + status=( + f"Previous Action: {top_result}\n\n" + "#pokemon #gameboy #nintendo #FediPlaysPokemon" + ), media_ids=[media_ids], ) - # try: - # post = self.mastodon.status_post(f"Previous Action: {top_result}\n\n#pokemon #gameboy #nintendo", media_ids=[media['id']]) - # except: - # time.sleep(30) - # post = self.mastodon.status_post(f"Previous Action: - # {top_result}\n\n#pokemon #gamebody #nintendo", - # media_ids=[media['id']]) + poll = self.retry_mastodon_call( self.post_poll, retries=5, @@ -233,12 +247,6 @@ def run(self): reply_id=post["id"], ) - # ry: - # poll = self.post_poll("Vote on the next action:", ["Up ⬆️", "Down ⬇️", "Right ➡️ ", "Left ⬅️", "🅰", "🅱", "Start", "Select"], reply_id=post['id']) - # except: - # time.sleep(30) - # poll = self.post_poll("Vote on the next action:", ["Up ⬆️", "Down ⬇️", "Right ➡️ ", "Left ⬅️", "🅰", "🅱", "Start", "Select"], reply_id=post['id']) - self.retry_mastodon_call( self.pin_posts, retries=5, @@ -246,13 +254,7 @@ def run(self): post_id=post["id"], poll_id=poll["id"], ) - # try: - # self.pin_posts(post['id'], poll['id']) - # except: - # time.sleep(30) - # self.pin_posts(post['id'], poll['id']) - # result = self.gameboy.build_gif("gif_images") result = False if result: gif = self.retry_mastodon_call( @@ -277,10 +279,11 @@ def run(self): self.gameboy.save() def test(self): + """Method used for testing""" self.gameboy.load() self.gameboy.get_recent_frames("screenshots", 25) # self.gameboy.build_gif("gif_images") - """while True: + while True: inp = input("Action: ") buttons = { "up": self.gameboy.dpad_up, @@ -290,12 +293,12 @@ def test(self): "a": self.gameboy.a, "b": self.gameboy.b, "start": self.gameboy.start, - "select": self.gameboy.select + "select": self.gameboy.select, } # Perform the corresponding action if inp.lower() in buttons: action = buttons[inp.lower()] - #self.gameboy.tick() + # self.gameboy.tick() action() frames = self.gameboy.loop_until_stopped() if frames > 51: @@ -306,9 +309,9 @@ def test(self): else: print(f"No action defined for '{inp}'.") self.gameboy.save() - #self.gameboy.build_gif("gif_images") - #self.take_action(inp) - #self.gameboy.tick(300)""" + # self.gameboy.build_gif("gif_images") + # self.take_action(inp) + # self.gameboy.tick(300) if __name__ == "__main__": diff --git a/gb.py b/gb.py index 451ed3d..3105fa7 100644 --- a/gb.py +++ b/gb.py @@ -1,12 +1,15 @@ +""" + Convenient class to interface with PyBoy +""" + import os import random import re import shutil -import threading import numpy as np from moviepy.editor import ImageSequenceClip -from PIL import Image, ImageDraw +from PIL import Image from pyboy import PyBoy, WindowEvent @@ -17,6 +20,7 @@ class Gameboy: rom (str): A string pointing to a rom file (MUST be GB or GBC, no GBA files) debug (bool, optional): Enable debug mode. Defaults to false """ + def __init__(self, rom, debug=False): self.debug = debug self.rom = rom @@ -25,29 +29,31 @@ def __init__(self, rom, debug=False): self.pyboy.set_emulation_speed(0) def is_running(self): - """ Returns True if bot is running in constant loop mode, false otherwise """ + """Returns True if bot is running in constant loop mode, false otherwise""" return self.running def run(self) -> None: - """ Continuously loop while pressing random buttons on the gameboy """ + """Continuously loop while pressing random buttons on the gameboy""" self.running = True while True: self.random_button() def tick(self, ticks=1, gif=True): - """ Advances the gameboy by a specified number of frames. + """Advances the gameboy by a specified number of frames. Args: ticks (int, optional): The number of frames to advance. Defaults to 1 gif (bool, optional): Generates screenshots for the gif if True """ - for tick in range(ticks): + for _ in range(ticks): if gif: self.screenshot("gif_images") self.pyboy.tick() def compare_frames(self, frame1, frame2): - """ Compares two frames from gameboy screenshot, returns a percentage difference between the two """ + """ + Compares two frames from gameboy screenshot, returns a percentage difference between the two + """ arr1 = np.array(frame1) arr2 = np.array(frame2) @@ -80,7 +86,7 @@ def get_recent_frames(self, directory, num_frames=100): return os.path.join(script_dir, "test.mp4") def empty_directory(self, directory): - """ Deletes all images in the provided directory """ + """Deletes all images in the provided directory""" image_files = [ i for i in os.listdir(directory) @@ -90,7 +96,7 @@ def empty_directory(self, directory): os.remove(os.path.join(directory, img)) def build_gif(self, image_path, delete=True, fps=120, output_name="action.mp4"): - """ Build a gif from a folder of images """ + """Build a gif from a folder of images""" # Get the directory of the current script script_dir = os.path.dirname(os.path.realpath(__file__)) gif_dir = os.path.join(script_dir, image_path) @@ -115,7 +121,6 @@ def build_gif(self, image_path, delete=True, fps=120, output_name="action.mp4"): if images: save_path = None - duration = int(1000 / fps) frames = images save_path = os.path.join(script_dir, output_name) clip = ImageSequenceClip(frames, fps=fps) @@ -128,11 +133,11 @@ def build_gif(self, image_path, delete=True, fps=120, output_name="action.mp4"): return False def stop(self): - """ Stops the continuous gameboy loop """ + """Stops the continuous gameboy loop""" self.running = False def load_rom(self, rom): - """ Loads the rom into a pyboy object """ + """Loads the rom into a pyboy object""" return PyBoy( rom, window_type="SDL2" if self.debug else "headless", @@ -142,13 +147,13 @@ def load_rom(self, rom): ) def dpad_up(self) -> None: - """ Presses up on the gameboy """ + """Presses up on the gameboy""" self.pyboy.send_input(WindowEvent.PRESS_ARROW_UP) self.tick(4) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_UP) def dpad_down(self) -> None: - """ Presses down on the gameboy """ + """Presses down on the gameboy""" self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) print("down") self.tick(3) @@ -156,7 +161,7 @@ def dpad_down(self) -> None: # self.tick() def dpad_right(self) -> None: - """ Presses right on the gameboy """ + """Presses right on the gameboy""" self.pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT) print("right") self.tick(3) @@ -164,7 +169,7 @@ def dpad_right(self) -> None: # self.tick() def dpad_left(self) -> None: - """ Presses left on the gameboy """ + """Presses left on the gameboy""" self.pyboy.send_input(WindowEvent.PRESS_ARROW_LEFT) print("left") self.tick(3) @@ -172,7 +177,7 @@ def dpad_left(self) -> None: # self.tick() def a(self) -> None: - """ Presses a on the gameboy """ + """Presses a on the gameboy""" self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) print("a") self.tick(3) @@ -180,7 +185,7 @@ def a(self) -> None: # self.tick() def b(self) -> None: - """ Presses b on the gameboy """ + """Presses b on the gameboy""" self.pyboy.send_input(WindowEvent.PRESS_BUTTON_B) print("b") self.tick(3) @@ -188,7 +193,7 @@ def b(self) -> None: # self.tick() def start(self) -> None: - """ Presses start on the gameboy """ + """Presses start on the gameboy""" self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) print("start") self.tick(3) @@ -196,14 +201,14 @@ def start(self) -> None: # self.tick() def select(self) -> None: - """ Presses select on the gameboy """ + """Presses select on the gameboy""" self.pyboy.send_input(WindowEvent.PRESS_BUTTON_SELECT) print("select") self.tick(3) self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_SELECT) def screenshot(self, path="screenshots"): - """ Takes a screenshot of gameboy screen and saves it to the path """ + """Takes a screenshot of gameboy screen and saves it to the path""" # Get the directory of the current script script_dir = os.path.dirname(os.path.realpath(__file__)) screenshot_dir = os.path.join(script_dir, path) @@ -227,7 +232,7 @@ def screenshot(self, path="screenshots"): return screenshot_path_full def random_button(self): - """ Picks a random button and presses it on the gameboy """ + """Picks a random button and presses it on the gameboy""" button = random.choice( [ self.dpad_up, @@ -243,20 +248,21 @@ def random_button(self): button() def load(self): - """ Loads the save state """ + """Loads the save state""" # Get the directory of the current script script_dir = os.path.dirname(os.path.realpath(__file__)) save_loc = os.path.join(script_dir, "save.state") + result = False if os.path.exists(save_loc): with open(save_loc, "rb") as file: self.pyboy.load_state(file) - return True + result = True else: print("Save state does not exist") - return False + return result def save(self): - """ Saves current state to a file """ + """Saves current state to a file""" # Get the directory of the current script script_dir = os.path.dirname(os.path.realpath(__file__)) save_loc = os.path.join(script_dir, "save.state") @@ -265,7 +271,7 @@ def save(self): self.pyboy.save_state(file) def loop_until_stopped(self, threshold=1): - """ Simulates the gameboy bot """ + """Simulates the gameboy bot""" script_dir = os.path.dirname(os.path.realpath(__file__)) running = True previous_frame = None