diff --git a/alas.py b/alas.py index 34871f274..d6ab4861f 100644 --- a/alas.py +++ b/alas.py @@ -42,6 +42,7 @@ class AzurLaneAutoScript: print(f'{key} = {value}') logger.hr('Settings saved') + self.config.config_check() def reward(self): for key, value in self.config.config['Reward'].items(): diff --git a/campaign/campaign_main/campaign_9_2.py b/campaign/campaign_main/campaign_9_2.py new file mode 100644 index 000000000..ecdd03710 --- /dev/null +++ b/campaign/campaign_main/campaign_9_2.py @@ -0,0 +1,78 @@ +from module.campaign.campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + + +MAP = CampaignMap('9-2') +MAP.shape = 'I5' +MAP.map_data = ''' + ++ ++ ME -- MB ME -- ME -- + MM ++ ME ME MB ME ++ ME -- + ME -- ME ++ ++ ++ ++ ME ++ + -- ++ -- -- ++ SP -- ME -- + -- ME -- SP ++ SP -- -- -- +''' +MAP.weight_data = ''' + 10 10 30 10 10 10 10 10 10 + 10 10 20 20 10 30 10 10 10 + 30 10 20 10 10 10 10 10 10 + 10 10 10 10 10 10 10 10 10 + 10 30 10 10 10 10 10 10 10 +''' +#MAP.camera_data = ['D4'] +MAP.spawn_data = [ + {'battle': 0, 'enemy': 3}, + {'battle': 1, 'enemy': 2}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 1}, + {'battle': 5, 'boss': 1}, + ] + +A1, B1, C1, D1, E1, F1, G1, H1, I1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, \ + = MAP.flatten() + +road_main = RoadGrids([C3, C2, [C1, D2], F1, H1, H2, H3, H4]) + +class Config: + INTERNAL_LINES_HOUGHLINES_THRESHOLD = 40 + EDGE_LINES_HOUGHLINES_THRESHOLD = 40 + COINCIDENT_POINT_ENCOURAGE_DISTANCE = 1.5 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (150, 255 - 24), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 24, 255), + 'prominence': 10, + 'distance': 50, + 'width': (0, 10), + 'wlen': 1000, + } + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if self.clear_roadblocks([road_main]): + return True + if self.clear_potential_roadblocks([road_main]): + return True + + return self.battle_default() + + def battle_5(self): + boss = self.map.select(is_boss=True) + if boss: + if not self.check_accessibility(boss[0], fleet=2): + if self.clear_roadblocks([road_main]): + return True + + return self.fleet_2.clear_boss() \ No newline at end of file diff --git a/campaign/campaign_main/campaign_9_4.py b/campaign/campaign_main/campaign_9_4.py new file mode 100644 index 000000000..d708b9b76 --- /dev/null +++ b/campaign/campaign_main/campaign_9_4.py @@ -0,0 +1,82 @@ +from module.campaign.campaign_base import CampaignBase +from module.map.map_base import CampaignMap +from module.map.map_grids import SelectedGrids, RoadGrids +from module.logger import logger + +MAP = CampaignMap('9-4') +MAP.shape = 'I6' + +MAP.map_data = ''' + MB -- ME -- ++ ++ ++ -- MB + -- ++ __ ME -- ME ME ME -- + ME -- ME -- ME -- __ ++ -- + ME -- ++ ++ -- -- ME __ ME + SP -- MA ++ ME -- ++ ME -- + SP -- -- ME -- ME ++ -- MB +''' + +MAP.weight_data = ''' + 10 10 20 10 10 10 10 10 10 + 10 10 10 20 10 10 10 10 10 + 10 10 20 10 20 10 10 10 10 + 20 10 10 10 10 10 10 10 10 + 10 10 10 10 10 10 10 10 10 + 10 10 10 10 10 10 10 10 10 +''' + +MAP.spawn_data = [ + {'battle': 0, 'enemy': 3}, + {'battle': 1, 'enemy': 2}, + {'battle': 2, 'enemy': 2}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'enemy': 2}, + {'battle': 5, 'boss': 1}, + ] + +A1, B1, C1, D1, E1, F1, G1, H1, I1, \ +A2, B2, C2, D2, E2, F2, G2, H2, I2, \ +A3, B3, C3, D3, E3, F3, G3, H3, I3, \ +A4, B4, C4, D4, E4, F4, G4, H4, I4, \ +A5, B5, C5, D5, E5, F5, G5, H5, I5, \ +A6, B6, C6, D6, E6, F6, G6, H6, I6, \ + = MAP.flatten() + +road_main = RoadGrids([A3, D6, [E5, F6], G4, I4]) + +class Config: + INTERNAL_LINES_HOUGHLINES_THRESHOLD = 40 + EDGE_LINES_HOUGHLINES_THRESHOLD = 40 + COINCIDENT_POINT_ENCOURAGE_DISTANCE = 1.5 + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (150, 255 - 24), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 24, 255), + 'prominence': 10, + 'distance': 50, + 'width': (0, 10), + 'wlen': 1000, + } + +class Campaign(CampaignBase): + MAP = MAP + + def battle_0(self): + if self.clear_roadblocks([road_main]): + return True + if self.clear_potential_roadblocks([road_main]): + return True + + return self.battle_default() + + def battle_5(self): + boss = self.map.select(is_boss=True) + if boss: + if not self.check_accessibility(boss[0], fleet=2): + if self.clear_roadblocks([road_main]): + return True + + return self.fleet_2.clear_boss() \ No newline at end of file diff --git a/dev_tools/grids_debug.py b/dev_tools/grids_debug.py new file mode 100644 index 000000000..56f153e87 --- /dev/null +++ b/dev_tools/grids_debug.py @@ -0,0 +1,83 @@ +import module.config.server as server + +server.server = 'cn' # Don't need to edit, it's used to avoid error. + +from module.map.grids import Grids +from module.config.config import AzurLaneConfig +from PIL import Image + +""" +This file is use to debug a perspective error. +It will call the map detection module (module/map/grids.py), outside Alas. +""" + +""" +Put image here. +""" +file = '' + + +class Config: + pass + + INTERNAL_LINES_HOUGHLINES_THRESHOLD = 40 + EDGE_LINES_HOUGHLINES_THRESHOLD = 40 + COINCIDENT_POINT_ENCOURAGE_DISTANCE = 1.5 + + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (150, 255 - 24), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 24, 255), + 'prominence': 10, + 'distance': 50, + 'width': (0, 10), + 'wlen': 1000 + } + + # These are the full configuration in module/config/config.py + """ + MAP_HAS_SIREN = False + MAP_HAS_DYNAMIC_RED_BORDER = False + MAP_SIREN_TEMPLATE = ['1', '2', '3', 'DD'] + + # Parameters for scipy.signal.find_peaks + # https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.find_peaks.html + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (150, 255 - 40), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 24, 255), + 'prominence': 10, + 'distance': 50, + # 'width': (0, 7), + 'wlen': 1000 + } + # Parameters for cv2.HoughLines + INTERNAL_LINES_HOUGHLINES_THRESHOLD = 75 + EDGE_LINES_HOUGHLINES_THRESHOLD = 75 + # Parameters for lines pre-cleansing + HORIZONTAL_LINES_THETA_THRESHOLD = 0.005 + VERTICAL_LINES_THETA_THRESHOLD = 18 + TRUST_EDGE_LINES = False # True to use edge to crop inner, false to use inner to crop edge + # Parameters for perspective calculating + VANISH_POINT_RANGE = ((540, 740), (-3000, -1000)) + DISTANCE_POINT_X_RANGE = ((-3200, -1600),) + # Parameters for line cleansing + COINCIDENT_POINT_ENCOURAGE_DISTANCE = 3 + ERROR_LINES_TOLERANCE = (-10, 10) + MID_DIFF_RANGE_H = (129 - 3, 129 + 3) + MID_DIFF_RANGE_V = (129 - 3, 129 + 3) + """ + + +cfg = AzurLaneConfig().merge(Config()) +grids = Grids(Image.open(file), cfg) +grids.predict() +grids.show() diff --git a/doc/debug_perspective_en.md b/doc/debug_perspective_en.md new file mode 100644 index 000000000..79cbdc801 --- /dev/null +++ b/doc/debug_perspective_en.md @@ -0,0 +1,143 @@ +# How to debug a perspective error + +## Normal logs + +This is an example log. + +``` +2020-06-03 00:44:46.221 | INFO | vanish_point: ( 646, -1736) +2020-06-03 00:44:46.222 | INFO | distant_point: (-2321, -1736) +2020-06-03 00:44:46.266 | INFO | 0.235s _ Horizontal: 5 (7 inner, 3 edge) +2020-06-03 00:44:46.266 | INFO | Edges: / \ Vertical: 9 (10 inner, 3 edge) +2020-06-03 00:44:46.273 | INFO | Center grid: (3, 1) +2020-06-03 00:44:46.493 | INFO | -- -- -- -- -- 2M -- -- +2020-06-03 00:44:46.501 | INFO | MY -- -- MY -- -- 3M -- +2020-06-03 00:44:46.501 | INFO | -- -- FL -- -- -- -- -- +2020-06-03 00:44:46.501 | INFO | -- 1L -- MY -- 2L -- +``` + + + +## Too few grid lines + +This may happens when it detected too few grid lines. + +``` File "E:\ProgramData\Pycharm\AzurLaneAutoScript\module\map\camera.py", line 114, in update + File "AzurLaneAutoScript\module\map\camera.py", line 114, in update + self.grids = Grids(self.device.image, config=self.config) + File "AzurLaneAutoScript\module\map\grids.py", line 19, in __init__ + super().__init__(image, config) + File "AzurLaneAutoScript\module\map\perspective.py", line 81, in __init__ + self.crossings = self.horizontal.cross(self.vertical) + File "AzurLaneAutoScript\module\map\perspective_items.py", line 170, in cross + points = np.vstack(self.cross_two_lines(self, other)) + File "lib\site-packages\numpy\core\shape_base.py", line 234, in vstack + return _nx.concatenate([atleast_2d(_m) for _m in tup], 0) + File "lib\site-packages\numpy\core\shape_base.py", line 234, in + return _nx.concatenate([atleast_2d(_m) for _m in tup], 0) + File "AzurLaneAutoScript\module\map\perspective_items.py", line 163, in cross_two_lines + for rho1, sin1, cos1 in zip(lines1.rho, lines1.sin, lines1.cos): +AttributeError: 'Lines' object has no attribute 'rho' +``` + +``` + File "AzurLaneAutoScript\module\map\camera.py", line 114, in update + self.grids = Grids(self.device.image, config=self.config) + File "AzurLaneAutoScript\module\map\grids.py", line 19, in __init__ + super().__init__(image, config) + File "AzurLaneAutoScript\module\map\perspective.py", line 98, in __init__ + self.horizontal, inner=inner_h.group(), edge=edge_h) + File "AzurLaneAutoScript\module\map\perspective.py", line 352, in line_cleanse + clean = self.mid_cleanse(origin, is_horizontal=lines.is_horizontal, threshold=threshold) + File "AzurLaneAutoScript\module\map\perspective.py", line 346, in mid_cleanse + mids = convert_to_y(mids) + File "AzurLaneAutoScript\module\map\perspective.py", line 277, in convert_to_y + return Points([[x, self.config.SCREEN_CENTER[1]] for x in xs], config=self.config) \ + File "AzurLaneAutoScript\module\map\perspective_items.py", line 15, in __init__ + self.x, self.y = self.points.T +ValueError: not enough values to unpack (expected 2, got 0) +``` + +Try reduce the threshold of `cv2.HoughLines`. Default is 75. + +Lower threshold means more lines. + +``` +INTERNAL_LINES_HOUGHLINES_THRESHOLD = 40 +EDGE_LINES_HOUGHLINES_THRESHOLD = 40 +``` + +Then you should also lower this and make a closer fit to the lines. + +Lower means closer fit, ignore more wrong lines. + +``` +COINCIDENT_POINT_ENCOURAGE_DISTANCE = 1.5 +``` + + + +## Camera outside map + +``` + File "AzurLaneAutoScript-master\module\map\camera.py", line 114, in update + self.grids = Grids(self.device.image, config=self.config) + File "AzurLaneAutoScript-master\module\map\grids.py", line 19, in __init__ + super().__init__(image, config) + File "AzurLaneAutoScript-master\module\map\perspective.py", line 98, in __init__ + self.horizontal, inner=inner_h.group(), edge=edge_h) + File "AzurLaneAutoScript-master\module\map\perspective.py", line 383, in line_cleanse + raise PerspectiveError('Camera outside map: to the %s' % ('upper' if lines.is_horizontal else 'right')) +module.exception.PerspectiveError: Camera outside map: to the upper +``` + +Alas can not handle if camera is not focusing on any map grid, it will swipe back if catches `Camera outside map`. This may happens when some inner lines are detected as edge lines. + +Try adjust the parameter of `scipy.signal.find_peaks`, the `height` value. + +- **height** Lower means detect lighter lines, 255 means pure black. +- **width** Line width, in pixels. +- **prominence** Line needs to be how much darker than surrounding pixels. +- **distance** Minimum distance between two detected point on line. +- **wlen** Maximum amount of data to be processed in one time. + +Know more about these parameters [here](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.find_peaks.html) + +``` +INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (150, 255 - 24), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, +} +EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 24, 255), + 'prominence': 10, + 'distance': 50, + 'width': (0, 10), + 'wlen': 1000 +} +``` + + + +## Coincident point unexpected + +``` +2020-05-31 20:04:47.397 | INFO | Horizontal coincident point unexpected: [-92.817316 141.99589405] +2020-05-31 20:04:48.509 | INFO | Vertical coincident point unexpected: [-692.01967461 141.68981244] +``` + +Try adjust the initial value of coincident point. if 141.99589405 is in (129 - 3, 129 + 3), it shut up. + +But remember, an incorrect value will ruin everything in map detection, it usually works fine with these logs show up. + +``` +MID_DIFF_RANGE_H = (129 - 3, 129 + 3) +MID_DIFF_RANGE_V = (129 - 3, 129 + 3) +``` + + + +## Too many deleted lines + diff --git a/module/base/timer.py b/module/base/timer.py index 66ba8fa00..ce3b6e2aa 100644 --- a/module/base/timer.py +++ b/module/base/timer.py @@ -84,6 +84,9 @@ class Timer: pass else: confirm_timer.reset() + + Also, It's a good idea to set `count`, to make alas run more stable on slow computers. + Expected speed is 0.35 second / screenshot. """ self.limit = limit self.count = count diff --git a/module/config/argparser.py b/module/config/argparser.py index 9d339a0ba..433dd3d09 100644 --- a/module/config/argparser.py +++ b/module/config/argparser.py @@ -141,12 +141,12 @@ def main(ini_name=''): f1.add_argument('--舰队步长1', default=default('--舰队步长1'), choices=['1', '2', '3', '4', '5', '6']) f2 = fleet.add_argument_group('BOSS队') - f2.add_argument('--舰队编号2', default=default('--舰队编号2'), choices=['不使用', '1', '2', '3', '4', '5', '6']) + f2.add_argument('--舰队编号2', default=default('--舰队编号2'), choices=['1', '2', '3', '4', '5', '6']) f2.add_argument('--舰队阵型2', default=default('--舰队阵型2'), choices=['单纵阵', '复纵阵', '轮形阵']) f2.add_argument('--舰队步长2', default=default('--舰队步长2'), choices=['1', '2', '3', '4', '5', '6']) f3 = fleet.add_argument_group('备用道中队') - f3.add_argument('--舰队编号3', default=default('--舰队编号3'), choices=['不使用', '1', '2', '3', '4', '5', '6']) + f3.add_argument('--舰队编号3', default=default('--舰队编号3'), choices=['1', '2', '3', '4', '5', '6']) f3.add_argument('--舰队阵型3', default=default('--舰队阵型3'), choices=['单纵阵', '复纵阵', '轮形阵']) f3.add_argument('--舰队步长3', default=default('--舰队步长3'), choices=['1', '2', '3', '4', '5', '6']) diff --git a/module/config/argparser_en.py b/module/config/argparser_en.py index 317ceab09..bbaa56b13 100644 --- a/module/config/argparser_en.py +++ b/module/config/argparser_en.py @@ -139,12 +139,12 @@ def main(ini_name=''): f1.add_argument('--fleet_step_1', default=default('--fleet_step_1'), choices=['1', '2', '3', '4', '5', '6'], help='In event map, fleet has limit on moving, so fleet_step is how far can a fleet goes in one operation, if map cleared, it will be ignored') f2 = fleet.add_argument_group('Boss Fleet') - f2.add_argument('--fleet_index_2', default=default('--fleet_index_2'), choices=['do_not_use', '1', '2', '3', '4', '5', '6']) + f2.add_argument('--fleet_index_2', default=default('--fleet_index_2'), choices=['1', '2', '3', '4', '5', '6']) f2.add_argument('--fleet_formation_2', default=default('--fleet_formation_2'), choices=['Line Ahead', 'Double Line', 'Diamond']) f2.add_argument('--fleet_step_2', default=default('--fleet_step_2'), choices=['1', '2', '3', '4', '5', '6'], help='In event map, fleet has limit on moving, so fleet_step is how far can a fleet goes in one operation, if map cleared, it will be ignored') f3 = fleet.add_argument_group('Alternate Mob Fleet') - f3.add_argument('--fleet_index_3', default=default('--fleet_index_3'), choices=['do_not_use', '1', '2', '3', '4', '5', '6']) + f3.add_argument('--fleet_index_3', default=default('--fleet_index_3'), choices=['1', '2', '3', '4', '5', '6']) f3.add_argument('--fleet_formation_3', default=default('--fleet_formation_3'), choices=['Line Ahead', 'Double Line', 'Diamond']) f3.add_argument('--fleet_step_3', default=default('--fleet_step_3'), choices=['1', '2', '3', '4', '5', '6'], help='In event map, fleet has limit on moving, so fleet_step is how far can a fleet goes in one operation, if map cleared, it will be ignored') diff --git a/module/config/config.py b/module/config/config.py index ee9b01791..1a4745407 100644 --- a/module/config/config.py +++ b/module/config/config.py @@ -375,6 +375,13 @@ class AzurLaneConfig: self.CONFIG_FILE = f'./config/{name}.ini' self.config.read_file(codecs.open(self.CONFIG_FILE, "r", "utf8")) self.load_from_config(self.config) + self.config_check() + + def config_check(self): + if self.FLEET_1 == self.FLEET_2: + logger.warning(f'Mob fleet [{self.FLEET_1}] and boss fleet [{self.FLEET_2}] is the same') + logger.warning('They should to be set to different fleets') + exit(1) def save(self): self.config.write(codecs.open(self.CONFIG_FILE, "w+", "utf8")) diff --git a/module/ui/ui.py b/module/ui/ui.py index 4c39c73f4..4182c9774 100644 --- a/module/ui/ui.py +++ b/module/ui/ui.py @@ -30,7 +30,7 @@ class UI(ModuleBase): logger.hr('UI click') if appear_button is None: appear_button = click_button - click_timer = Timer(retry_wait) + click_timer = Timer(retry_wait, count=retry_wait // 0.5) while 1: if skip_first_screenshot: skip_first_screenshot = False