diff --git a/bot.py b/bot.py index 8631e14..8048c0d 100644 --- a/bot.py +++ b/bot.py @@ -1,64 +1,78 @@ -from gb import Gameboy +""" + A bot that interacts with a Mastodon compatible API, plays gameboy games via a Poll +""" + +import os import random import time -from mastodon import Mastodon + import toml -import os +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 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", 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', {}) + 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): + """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]() + # print(self.gameboy.is_running()) + # 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, - "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,43 +80,59 @@ def random_button(self): action() return random_button - def login(self): - server = self.mastodon_config.get('server') + """Logs into the mastodon server using config credentials""" + 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): + """Posts a poll to Mastodon compatible server""" + 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 + """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: - 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", encoding="utf-8") 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 + 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, @@ -111,7 +141,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 @@ -121,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 + 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 @@ -138,18 +172,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 +195,95 @@ 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: - # 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", 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, interval=10, status="Vote on the next action:\n\n#FediPlaysPokemon", options=["Up ⬆️", "Down ⬇️", "Right ➡️ ", "Left ⬅️", "🅰", "🅱", "Start", "Select"], 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, interval=10, 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") + 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"]] + + 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], + ) + + 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"], + ) + + self.retry_mastodon_call( + self.pin_posts, + retries=5, + interval=10, + post_id=post["id"], + poll_id=poll["id"], + ) + 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): + """Method used for testing""" 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, @@ -221,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: @@ -237,16 +309,16 @@ 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__': + +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..3105fa7 100644 --- a/gb.py +++ b/gb.py @@ -1,14 +1,25 @@ -from pyboy import PyBoy, WindowEvent -import random -import threading +""" + Convenient class to interface with PyBoy +""" + import os +import random import re import shutil -from PIL import Image, ImageDraw -from moviepy.editor import ImageSequenceClip + import numpy as np +from moviepy.editor import ImageSequenceClip +from PIL import Image +from pyboy import PyBoy, WindowEvent + 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 @@ -17,27 +28,32 @@ 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): - for tick in range(ticks): + """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 _ 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,173 +64,214 @@ 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 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") + 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): - 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 +281,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