Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUI #234

Merged
merged 26 commits into from
Sep 13, 2024
Merged

GUI #234

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5c044d0
Move config parsing outside of Bot class and add GUI
Sep 10, 2024
e5f654b
WIP: new GUI and trying to intercept logs to display in window
Sep 10, 2024
ab00286
Update main.py
Leviaria Sep 10, 2024
799169e
Update bot.py
Leviaria Sep 10, 2024
42bcb29
Create utils.py
Leviaria Sep 10, 2024
6e1112c
Add files via upload
Leviaria Sep 10, 2024
f1462f1
Merge branch 'gui-pog' into fork/Leviaria/main
Sep 10, 2024
7903cd4
init options with real config data + visualizer widget
Sep 10, 2024
e21d700
add white font to every text elem
Sep 10, 2024
44d5e7b
add play pause button
Sep 10, 2024
c0e6552
add weird animation on start stop button
Sep 10, 2024
1f23d9e
lint
Sep 10, 2024
e0d3efe
show logs in GUI
Sep 11, 2024
3ae4a88
"save config" button in gui
Sep 11, 2024
de66739
round play_action to 2 decimal places when saving config
Sep 11, 2024
b45be26
setup logger again if we changed log level (else not applied)
Sep 12, 2024
feb4529
button name
Sep 12, 2024
8fb9ca2
add dependencies
Sep 12, 2024
591a170
add Qt6 to pylint whitelist because C extension raise false positives
Sep 12, 2024
fa0a67a
Add clearer names to config items
Sep 13, 2024
178a5de
Update main_window.py
Leviaria Sep 13, 2024
cd959d0
Update layout_setup.py
Leviaria Sep 13, 2024
6a4d346
Add files via upload
Leviaria Sep 13, 2024
d9c2d27
Merge branch 'main' into main
Leviaria Sep 13, 2024
267bf1c
Add banner to indicate that the visualizer is disabled
Sep 13, 2024
3d2f992
Merge remote-tracking branch 'Leviaria/main' into fork/Leviaria/main
Sep 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension-pkg-allow-list=
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=
extension-pkg-whitelist=PyQt6

# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
Expand Down
60 changes: 22 additions & 38 deletions clashroyalebuildabot/bot/bot.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import os
import random
import sys
import threading
import time

import keyboard
from loguru import logger
import yaml

from clashroyalebuildabot.constants import ALL_TILES
from clashroyalebuildabot.constants import ALLY_TILES
from clashroyalebuildabot.constants import DEBUG_DIR
from clashroyalebuildabot.constants import DISPLAY_CARD_DELTA_X
from clashroyalebuildabot.constants import DISPLAY_CARD_HEIGHT
from clashroyalebuildabot.constants import DISPLAY_CARD_INIT_X
Expand All @@ -19,7 +15,6 @@
from clashroyalebuildabot.constants import DISPLAY_HEIGHT
from clashroyalebuildabot.constants import LEFT_PRINCESS_TILES
from clashroyalebuildabot.constants import RIGHT_PRINCESS_TILES
from clashroyalebuildabot.constants import SRC_DIR
from clashroyalebuildabot.constants import TILE_HEIGHT
from clashroyalebuildabot.constants import TILE_INIT_X
from clashroyalebuildabot.constants import TILE_INIT_Y
Expand All @@ -39,22 +34,17 @@ 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._setup_logger()
self.should_run = True

cards = [action.CARD for action in actions]
if len(cards) != 8:
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 All @@ -78,34 +68,24 @@ def _log_and_wait(prefix, delay):
logger.info(message)
time.sleep(delay)

@staticmethod
def _setup_logger():
config_path = os.path.join(SRC_DIR, "config.yaml")
with open(config_path, encoding="utf-8") as file:
config = yaml.safe_load(file)
log_level = config.get("bot", {}).get("log_level", "INFO").upper()
logger.remove()
logger.add(sys.stdout, level=log_level)
logger.add(
os.path.join(DEBUG_DIR, "bot.log"),
rotation="500 MB",
level=log_level,
)

@staticmethod
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 +212,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
29 changes: 29 additions & 0 deletions clashroyalebuildabot/gui/animations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from PyQt6.QtCore import QEasingCurve
from PyQt6.QtCore import QPropertyAnimation
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QGraphicsDropShadowEffect


def start_play_button_animation(self):
self.glow_effect = QGraphicsDropShadowEffect(self)
self.glow_effect.setBlurRadius(
10
) # Initial blur radius for the glow effect
self.glow_effect.setColor(Qt.GlobalColor.cyan)
self.glow_effect.setOffset(
0, 0
) # Center the shadow to create the glow effect
self.start_stop_button.setGraphicsEffect(self.glow_effect)

_start_glow_animation(self)


def _start_glow_animation(self):
"""Create a glow effect animation."""
self.glow_animation = QPropertyAnimation(self.glow_effect, b"blurRadius")
self.glow_animation.setStartValue(0)
self.glow_animation.setEndValue(25)
self.glow_animation.setDuration(2000)
self.glow_animation.setEasingCurve(QEasingCurve.Type.SineCurve)
self.glow_animation.setLoopCount(-1)
self.glow_animation.start()
45 changes: 45 additions & 0 deletions clashroyalebuildabot/gui/gameplay_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from PyQt6.QtGui import QImage
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import QLabel
from PyQt6.QtWidgets import QVBoxLayout
from PyQt6.QtWidgets import QWidget


class ImageStreamWindow(QWidget):
def __init__(self):
super().__init__()

self.image = QLabel(self)
self.inactiveIndicator = QLabel(self)
self.inactiveIndicator.setText(
"The visualizer is disabled. Enable it in the Settings tab."
)
self.inactiveIndicator.setStyleSheet(
"background-color: #FFA500; color: white; padding: 5px; height: fit-content; width: fit-content;"
)
self.inactiveIndicator.setMaximumHeight(30)
layout = QVBoxLayout()
layout.addWidget(self.inactiveIndicator)
layout.addWidget(self.image)
self.setLayout(layout)

def update_frame(self, annotated_image):
height, width, channel = annotated_image.shape
bytes_per_line = 3 * width
q_image = QImage(
annotated_image.data.tobytes(),
width,
height,
bytes_per_line,
QImage.Format.Format_RGB888,
)

pixmap = QPixmap.fromImage(q_image)
self.image.setPixmap(pixmap)

def update_active_state(self, active):
if not active:
self.inactiveIndicator.show()
else:
self.inactiveIndicator.hide()
self.image.clear()
Loading