Refactor: More compact route files

This commit is contained in:
LmeSzinc 2023-09-24 06:52:52 +08:00
parent 6eb9317ac4
commit 3fa28fba24
7 changed files with 192 additions and 19 deletions

View File

@ -0,0 +1,89 @@
import os
import re
from dataclasses import dataclass, fields
from module.base.code_generator import CodeGenerator
@dataclass
class RouteData:
name: str
route: str
plane: str
floor: str = 'F1'
position: tuple = None
class RouteExtract:
def __init__(self, folder):
self.folder = folder
def iter_files(self):
for path, folders, files in os.walk(self.folder):
path = path.replace('\\', '/')
for file in files:
if file.endswith('.py'):
yield f'{path}/{file}'
def extract_route(self, file):
print(f'Extract {file}')
with open(file, 'r', encoding='utf-8') as f:
content = f.read()
"""
def route_item_enemy(self):
self.enter_himeko_trial()
self.map_init(plane=Jarilo_BackwaterPass, position=(519.9, 361.5))
"""
regex = re.compile(
r'def (?P<func>[a-zA-Z0-9_]*?)\(self\):.*?'
r'self\.map_init\((.*?)\)'
, re.DOTALL)
file = file.replace(self.folder, '').replace('.py', '').replace('/', '_').strip('_')
module = f"{self.folder.strip('./').replace('/', '.')}.{file.replace('_', '.')}"
for result in regex.findall(content):
func, data = result
res = re.search(r'plane=([a-zA-Z_]*)', data)
if res:
plane = res.group(1)
else:
# Must contain plane
continue
res = re.search(r'floor=([\'"a-zA-Z0-9_]*)', data)
if res:
floor = res.group(1).strip('"\'')
else:
floor = 'F1'
res = re.search(r'position=\(([0-9.]*)[, ]+([0-9.]*)', data)
if res:
position = (float(res.group(1)), float(res.group(2)))
else:
position = None
yield RouteData(
name=f'{file}__{func}',
route=f'{module}:{func}',
plane=plane,
floor=floor,
position=position,
)
def write(self, file):
gen = CodeGenerator()
gen.Import("""
from tasks.map.route.base import RouteData
""")
for f in self.iter_files():
for row in self.extract_route(f):
with gen.Object(key=row.name, object_class='RouteData'):
for key in fields(row):
value = getattr(row, key.name)
gen.ObjectAttr(key.name, value)
gen.write(file)
if __name__ == '__main__':
os.chdir(os.path.join(os.path.dirname(__file__), '../'))
RouteExtract('./route/daily').write('./tasks/map/route/route/daily.py')

View File

@ -23,6 +23,7 @@ from tasks.dungeon.ui import DungeonUI
from tasks.item.consumable_usage import ConsumableUsageUI
from tasks.item.relics import RelicsUI
from tasks.map.route.loader import RouteLoader
from tasks.map.route.route import ROUTE_DAILY
class DailyQuestOcr(Ocr):
@ -260,7 +261,7 @@ class DailyQuestUI(DungeonUI, RouteLoader):
if RelicsUI(self.config, self.device).salvage_relic():
done += 1
if KEYWORDS_DAILY_QUEST.Complete_Forgotten_Hall_1_time in quests:
self.route_run('daily.forgotten_hall.stage_1')
self.route_run(ROUTE_DAILY.ForgottenHallStage1__route)
done += 1
return done

View File

@ -1,8 +1,19 @@
from dataclasses import dataclass
from tasks.map.control.control import MapControl
from tasks.map.control.waypoint import Waypoint
from tasks.map.keywords import MapPlane
@dataclass
class RouteData:
name: str
route: str
plane: str
floor: str = 'F1'
position: tuple = None
class RouteBase(MapControl):
"""
Base class of `Route`

View File

@ -1,38 +1,71 @@
import importlib
import os
from module.exception import RequestHumanTakeover
from module.base.decorator import del_cached_property
from module.exception import ScriptError
from module.logger import logger
from tasks.base.ui import UI
from tasks.map.route.base import RouteBase
from tasks.map.route.base import RouteBase, RouteData
class RouteLoader(UI):
route: RouteBase
route_module: str = ''
route_func: str = ''
route_obj: RouteBase
def route_run(self, route: str):
def route_delete(self):
del_cached_property(self, 'route_obj')
self.route_module = ''
self.route_func = ''
def route_run(self, route: RouteData | str):
"""
Args:
route: .py module path such as `daily.forgotten_hall.stage1`
which will load `./route/daily/forgotten_hall/stage1.py`
route: .py module path such as `route.daily.ForgottenHallStage1:route`
which will load `./route/daily/ForgottenHallStage1.py` and run `Route.route()`
"""
folder, name = route.rsplit('.', maxsplit=1)
path = f'./route/{route.replace(".", "/")}.py'
logger.hr('Route run', level=1)
if isinstance(route, RouteData):
route = route.route
logger.attr('Route', route)
try:
module = importlib.import_module(f'route.{folder}.{name}')
module, func = route.split(':')
except ValueError:
logger.critical(f'Route invalid: {route}')
raise ScriptError
path = f'./{module.replace(".", "/")}.py'
# Import route file
try:
module_obj = importlib.import_module(f'{module}')
except ModuleNotFoundError:
logger.critical(f'Route file not found: {route} ({path})')
logger.critical(f'Route file not found: {module} ({path})')
if not os.path.exists(path):
logger.critical(f'Route file not exists: {path}')
raise RequestHumanTakeover
raise ScriptError
# Create route object
# Reuse the previous one
if self.route_module != module:
# config = copy.deepcopy(self.config).merge(module.Config())
config = self.config
device = self.device
try:
self.route = module.Route(config=config, device=device)
return self.route.route()
self.route_obj = module_obj.Route(config=config, device=device)
except AttributeError as e:
logger.critical(e)
logger.critical(f'Route file {route} ({path}) must define Route.route()')
raise RequestHumanTakeover
logger.critical(f'Route file {route} ({path}) must define class Route')
raise ScriptError
self.route_module = module
# Get route func
try:
func_obj = self.route_obj.__getattribute__(func)
except AttributeError as e:
logger.critical(e)
logger.critical(f'Route class in {route} ({path}) does not have method {func}')
raise ScriptError
self.route_func = func
# Run
func_obj()

View File

@ -0,0 +1 @@
import tasks.map.route.route.daily as ROUTE_DAILY

View File

@ -0,0 +1,38 @@
from tasks.map.route.base import RouteData
ForgottenHallStage1__route = RouteData(
name='ForgottenHallStage1__route',
route='route.daily.ForgottenHallStage1:route',
plane='Jarilo_BackwaterPass',
floor='F1',
position=(369.4, 643.4),
)
HimekoTrial__route_item_enemy = RouteData(
name='HimekoTrial__route_item_enemy',
route='route.daily.HimekoTrial:route_item_enemy',
plane='Jarilo_BackwaterPass',
floor='F1',
position=(519.9, 361.5),
)
HimekoTrial__route_item = RouteData(
name='HimekoTrial__route_item',
route='route.daily.HimekoTrial:route_item',
plane='Jarilo_BackwaterPass',
floor='F1',
position=(519.9, 361.5),
)
HimekoTrial__route_enemy = RouteData(
name='HimekoTrial__route_enemy',
route='route.daily.HimekoTrial:route_enemy',
plane='Jarilo_BackwaterPass',
floor='F1',
position=(519.9, 361.5),
)
HimekoTrial__exit = RouteData(
name='HimekoTrial__exit',
route='route.daily.HimekoTrial:exit',
plane='Jarilo_BackwaterPass',
floor='F1',
position=(519.9, 361.5),
)