Skip to content

Commit

Permalink
Move config parsing outside of Bot class and add GUI
Browse files Browse the repository at this point in the history
  • Loading branch information
fe-art committed Sep 10, 2024
1 parent 5bee14c commit 5c044d0
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 21 deletions.
39 changes: 22 additions & 17 deletions clashroyalebuildabot/bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ class Bot:
is_paused_logged = False
is_resumed_logged = True

def __init__(self, actions, auto_start=True):
def __init__(self, actions, config):
self.actions = actions
self.auto_start = auto_start
self.auto_start = config["bot"]["auto_start_game"]
self.end_of_game_clicked = False
self.should_run = True # always true unless GUI mode is used

self._setup_logger()

Expand All @@ -51,10 +52,6 @@ def __init__(self, actions, auto_start=True):
raise ValueError(f"Must provide 8 cards but was given: {cards}")
self.cards_to_actions = dict(zip(cards, actions))

config_path = os.path.join(SRC_DIR, "config.yaml")
with open(config_path, encoding="utf-8") as file:
config = yaml.safe_load(file)

self.visualizer = Visualizer(**config["visuals"])
self.emulator = Emulator(**config["adb"])
self.detector = Detector(cards=cards)
Expand Down Expand Up @@ -96,16 +93,20 @@ def _setup_logger():
def _handle_keyboard_shortcut():
while True:
keyboard.wait("ctrl+p")
if pause_event.is_set():
logger.info("Bot paused.")
pause_event.clear()
Bot.is_paused_logged = True
Bot.is_resumed_logged = False
else:
logger.info("Bot resumed.")
pause_event.set()
Bot.is_resumed_logged = True
Bot.is_paused_logged = False
Bot.pause_or_resume()

@staticmethod
def pause_or_resume():
if pause_event.is_set():
logger.info("Bot paused.")
pause_event.clear()
Bot.is_paused_logged = True
Bot.is_resumed_logged = False
else:
logger.info("Bot resumed.")
pause_event.set()
Bot.is_resumed_logged = True
Bot.is_paused_logged = False

@staticmethod
def _get_nearest_tile(x, y):
Expand Down Expand Up @@ -232,11 +233,15 @@ def step(self):

def run(self):
try:
while True:
while self.should_run:
if not pause_event.is_set():
time.sleep(0.1)
continue

self.step()
logger.info("Thanks for using CRBAB, see you next time!")
except KeyboardInterrupt:
logger.info("Thanks for using CRBAB, see you next time!")

def stop(self):
self.should_run = False
4 changes: 3 additions & 1 deletion clashroyalebuildabot/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ bot:
log_level: "DEBUG"

# Create a deck code when the game starts
load_deck: True
load_deck: False
auto_start_game: False
enable_gui: True

adb:
# The IP address of your device or emulator.
Expand Down
108 changes: 108 additions & 0 deletions clashroyalebuildabot/gui/gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import tkinter as tk
from threading import Thread
from typing import List, Type

from clashroyalebuildabot import Bot
from clashroyalebuildabot.actions.generic.action import Action


class Gui:
def __init__(self, config, actions: List[Type[Action]]):
self.bot = None
self.config = config
self.actions = actions
self.bot_thread = None
self.running = False

self.save_labels = None
self.save_images = None
self.show_images = None
self.load_deck = None
self.auto_start_game = None
self.action_delay = None

def run(self):
root = tk.Tk()
root.title("CRBAB MENU")
root.minsize(400, 350)

start_button = tk.Button(root, text="Start bot ✅", command=self.start_bot, bg="lightgreen")
start_button.pack(pady=10)

start_button = tk.Button(root, text="Pause / Resume bot", command=Bot.pause_or_resume)
start_button.pack(pady=10)

stop_button = tk.Button(root, text="Stop bot ❌", command=self.stop_bot, bg="lightcoral")
stop_button.pack(pady=10)

restart_button = tk.Button(root, text="Update + restart bot 🔄", command=self.restart_bot, bg="lightblue")
restart_button.pack(pady=10)

self.save_labels = tk.IntVar(value=self.config["visuals"]["save_labels"])
save_labels_checkbox = tk.Checkbutton(root, text="Save labels", variable=self.save_labels)
save_labels_checkbox.pack(pady=5)

self.save_images = tk.IntVar(value=self.config["visuals"]["save_images"])
save_images_checkbox = tk.Checkbutton(root, text="Save images", variable=self.save_images)
save_images_checkbox.pack(pady=5)

self.show_images = tk.IntVar(value=self.config["visuals"]["show_images"])
show_images_checkbox = tk.Checkbutton(root, text="Show images", variable=self.show_images)
show_images_checkbox.pack(pady=5)

self.load_deck = tk.IntVar(value=self.config["bot"]["load_deck"])
load_deck_checkbox = tk.Checkbutton(root, text="Load Deck", variable=self.load_deck)
load_deck_checkbox.pack(pady=5)

self.auto_start_game = tk.IntVar(value=self.config["bot"]["auto_start_game"])
auto_start_game_checkbox = tk.Checkbutton(root, text="Auto start game", variable=self.auto_start_game)
auto_start_game_checkbox.pack(pady=5)

# Log Level Dropdown
action_delays = [0.5, 1, 1.25, 1.5]
self.action_delay = tk.StringVar(value=self.config["ingame"]["play_action"])
action_delay_menu = tk.OptionMenu(root, self.action_delay, *action_delays)
tk.Label(root, text="Action delay").pack(pady=5)
action_delay_menu.pack(pady=5)

exit_button = tk.Button(root, text="Exit", command=root.quit)
exit_button.pack(pady=10)

root.mainloop()

def restart_bot(self):
"""Restart the bot by stopping the current instance and starting a new one with the updated config."""
if self.running:
self.stop_bot()
self.update_config()
self.start_bot()

def update_config(self):
"""Update the config with the current state of the checkboxes."""
self.config["visuals"]["save_labels"] = self.save_labels.get()
self.config["visuals"]["save_images"] = self.save_images.get()
self.config["visuals"]["show_images"] = self.show_images.get()
self.config["bot"]["load_deck"] = self.load_deck.get()
self.config["bot"]["auto_start_game"] = self.auto_start_game.get()
self.config["ingame"]["play_action"] = float(self.action_delay.get())

def bot_task(self):
self.bot = Bot(actions=self.actions, config=self.config)
self.bot.run()

def start_bot(self):
"""Start the bot in a separate thread to keep the GUI responsive."""
if self.running:
return
self.update_config()
self.running = True
self.bot_thread = Thread(target=self.bot_task)
self.bot_thread.daemon = True # Daemon thread will close when the program exits
self.bot_thread.start()

def stop_bot(self):
if not self.bot:
return
self.bot.stop()
self.running = False

25 changes: 22 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import os
from datetime import datetime
import signal
import sys
import threading
import time

import yaml
from loguru import logger

from clashroyalebuildabot.actions import ArchersAction
Expand All @@ -15,6 +17,8 @@
from clashroyalebuildabot.actions import MusketeerAction
from clashroyalebuildabot.actions import WitchAction
from clashroyalebuildabot.bot import Bot
from clashroyalebuildabot.constants import SRC_DIR
from clashroyalebuildabot.gui.gui import Gui

start_time = datetime.now()

Expand All @@ -37,6 +41,13 @@ def update_terminal_title():
sys.stdout.flush()
time.sleep(1)

def load_config():
try:
config_path = os.path.join(SRC_DIR, "config.yaml")
with open(config_path, encoding="utf-8") as file:
return yaml.safe_load(file)
except Exception as e:
logger.error(f"Can't parse config, stacktrace: {e}")

def main():
actions = [
Expand All @@ -50,10 +61,18 @@ def main():
WitchAction,
]
try:
bot = Bot(actions=actions)
bot.run()
config = load_config()

# GUI MODE
if config["bot"]["enable_gui"]:
gui = Gui(config=config, actions=actions)
gui.run()
else:
# REGULAR MODE
bot = Bot(actions=actions, config=config)
bot.run()
except Exception as e:
logger.error(f"An error occurred: {e}")
logger.error(f"An error occurred in main loop: {e}")
sys.exit(1)


Expand Down

0 comments on commit 5c044d0

Please sign in to comment.