From 8366d7a258b8bf3346a1464c26889f2f447abd5f Mon Sep 17 00:00:00 2001 From: LmeSzinc Date: Mon, 30 Mar 2020 12:27:18 +0800 Subject: [PATCH] =?UTF-8?q?Add:=20=E6=B4=BB=E5=8A=A8AB=E5=9B=BE=E6=AF=8F?= =?UTF-8?q?=E6=97=A5=E4=B8=89=E5=80=8D=E5=9F=BA=E6=9C=AC=E7=A8=B3=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整了微层混合透视识别的参数 - 增加了在hsv色彩空间下的识别方法 - 增加了对地面闪烁红框的识别 - 增加了已打精英的计数 - 修改精英识别, boss识别, 舰队识别方法为hsv颜色计数 - 修复了自动生成camera_data的报错 --- campaign/event_20200326_cn/a1.py | 6 ++- campaign/event_20200326_cn/a2.py | 3 ++ campaign/event_20200326_cn/a3.py | 3 ++ campaign/event_20200326_cn/b1.py | 5 ++- module/campaign/run.py | 72 +++++++++++++++++++------------- module/config/config.py | 1 + module/event/campaign_ab.py | 4 +- module/map/camera.py | 4 +- module/map/fleet.py | 15 +++++-- module/map/grid_predictor.py | 41 +++++++++++++----- module/map/map.py | 12 ++++-- module/map/map_base.py | 30 +++++++------ module/map/map_operation.py | 2 +- module/map/perspective.py | 2 +- 14 files changed, 135 insertions(+), 65 deletions(-) diff --git a/campaign/event_20200326_cn/a1.py b/campaign/event_20200326_cn/a1.py index 695945c07..0eff0a0f8 100644 --- a/campaign/event_20200326_cn/a1.py +++ b/campaign/event_20200326_cn/a1.py @@ -37,7 +37,7 @@ class Config: CAMPAIGN_MODE = 'normal' INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { - 'height': (50, 200), + 'height': (50, 255 - 80), 'width': 1, 'prominence': 10, 'distance': 35, @@ -46,6 +46,7 @@ class Config: 'height': (255 - 80, 255), 'prominence': 10, 'distance': 50, + 'width': (0, 7), 'wlen': 1000 } @@ -64,3 +65,6 @@ class Campaign(CampaignBase): return True return self.battle_default() + + def battle_3(self): + return self.clear_boss() diff --git a/campaign/event_20200326_cn/a2.py b/campaign/event_20200326_cn/a2.py index 6470d0d95..875e17c8e 100644 --- a/campaign/event_20200326_cn/a2.py +++ b/campaign/event_20200326_cn/a2.py @@ -50,3 +50,6 @@ class Campaign(CampaignBase): return True return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/event_20200326_cn/a3.py b/campaign/event_20200326_cn/a3.py index 684af0287..984822a8f 100644 --- a/campaign/event_20200326_cn/a3.py +++ b/campaign/event_20200326_cn/a3.py @@ -57,3 +57,6 @@ class Campaign(CampaignBase): return True return self.battle_default() + + def battle_4(self): + return self.clear_boss() \ No newline at end of file diff --git a/campaign/event_20200326_cn/b1.py b/campaign/event_20200326_cn/b1.py index 7cd038a6d..21d1b3aec 100644 --- a/campaign/event_20200326_cn/b1.py +++ b/campaign/event_20200326_cn/b1.py @@ -5,7 +5,7 @@ from module.logger import logger from campaign.event_20200326_cn.a1 import Config MAP = CampaignMap() -MAP.shape = 'G7' +MAP.shape = 'H7' MAP.map_data = ''' -- ME -- ME ++ ++ SP SP ME -- MS -- ME -- -- SP @@ -48,3 +48,6 @@ class Campaign(CampaignBase): return True return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/module/campaign/run.py b/module/campaign/run.py index a06d4fd84..9bdcfd1ab 100644 --- a/module/campaign/run.py +++ b/module/campaign/run.py @@ -20,6 +20,7 @@ class CampaignRun(CampaignUI): module = None config: AzurLaneConfig campaign: CampaignBase + run_count: int def load_campaign(self, name, folder='campaign_main'): """ @@ -67,57 +68,72 @@ class CampaignRun(CampaignUI): os.mkdir(folder) self.campaign.config.SCREEN_SHOT_SAVE_FOLDER = folder - def oil_check(self): + def triggered_stop_condition(self): """ - Returns: - bool: If have enough oil. - """ - if not self.config.STOP_IF_OIL_LOWER_THAN: - return True - self.device.screenshot() - return OCR_OIL.ocr(self.device.image) > self.config.STOP_IF_OIL_LOWER_THAN - def run(self, name, folder='campaign_main'): + Returns: + bool: If triggered a stop condition. + """ + # Run count limit + if self.run_count >= self.config.STOP_IF_COUNT_GREATER_THAN > 0: + logger.hr('Triggered count stop') + return True + # Run time limit + if self.config.STOP_IF_TIME_REACH and datetime.now() > self.config.STOP_IF_TIME_REACH: + logger.hr('Triggered time limit') + self.config.config.set('Setting', 'if_time_reach', '0') + self.config.save() + return True + # Oil limit + if self.config.STOP_IF_TRIGGER_EMOTION_LIMIT and self.campaign.config.EMOTION_LIMIT_TRIGGERED: + logger.hr('Triggered emotion limit') + return True + # Emotion limit + if self.config.STOP_IF_OIL_LOWER_THAN: + if OCR_OIL.ocr(self.device.image) < self.config.STOP_IF_OIL_LOWER_THAN: + logger.hr('Triggered oil limit') + return True + + return False + + def run(self, name, folder='campaign_main', total=0): """ Args: name (str): Name of .py file. folder (str): Name of the file folder under campaign. + total (int): """ self.load_campaign(name, folder=folder) - n = 0 + self.run_count = 0 while 1: + self.device.screenshot() + # End - if n >= self.config.STOP_IF_COUNT_GREATER_THAN > 0: - logger.hr('Triggered count stop') + if total and self.run_count == total: break - if not self.oil_check(): - logger.hr('Triggered oil limit') - break - if self.config.STOP_IF_TIME_REACH and datetime.now() > self.config.STOP_IF_TIME_REACH: - logger.hr('Triggered time limit') - self.config.config.set('Setting', 'if_time_reach', '0') - self.config.save() - break - if self.config.STOP_IF_TRIGGER_EMOTION_LIMIT and self.campaign.config.EMOTION_LIMIT_TRIGGERED: - logger.hr('Triggered emotion limit') + if self.triggered_stop_condition(): break # Log logger.hr(name, level=1) if self.config.STOP_IF_COUNT_GREATER_THAN > 0: - logger.info(f'Count: [{n}/{self.config.STOP_IF_COUNT_GREATER_THAN}]') + logger.info(f'Count: [{self.run_count}/{self.config.STOP_IF_COUNT_GREATER_THAN}]') else: - logger.info(f'Count: [{n}]') + logger.info(f'Count: [{self.run_count}]') # Run - self.ensure_campaign_ui(name=self.stage) - self.campaign.ENTRANCE = self.campaign_get_entrance(name=self.stage) + self.campaign.device.image = self.device.image + if self.campaign.is_in_map(): + logger.info('Already in map, skip ensure_campaign_ui.') + else: + self.ensure_campaign_ui(name=self.stage) + self.campaign.ENTRANCE = self.campaign_get_entrance(name=self.stage) self.campaign.run() # After run - n += 1 + self.run_count += 1 if self.config.STOP_IF_COUNT_GREATER_THAN > 0: - count = self.config.STOP_IF_COUNT_GREATER_THAN - n + count = self.config.STOP_IF_COUNT_GREATER_THAN - self.run_count count = 0 if count < 0 else count self.config.config.set('Setting', 'if_count_greater_than', str(count)) self.config.save() diff --git a/module/config/config.py b/module/config/config.py index d617bc63d..95ceae871 100644 --- a/module/config/config.py +++ b/module/config/config.py @@ -196,6 +196,7 @@ class AzurLaneConfig: 'height': (255 - 24, 255), 'prominence': 10, 'distance': 50, + 'width': (0, 7), 'wlen': 1000 } # Parameters for cv2.HoughLines diff --git a/module/event/campaign_ab.py b/module/event/campaign_ab.py index 607d15f54..62f2c55ab 100644 --- a/module/event/campaign_ab.py +++ b/module/event/campaign_ab.py @@ -6,11 +6,11 @@ CAMPAIGN_NAME = ['a1', 'a2', 'a3', 'b1', 'b2', 'b3'] class CampaignAB(CampaignRun): - def run(self, name, folder='campaign_main'): + def run(self, name, folder='campaign_main', total=0): name = name.lower() option = ('EventABRecord', name) if not self.config.record_executed_since(option=option, since=RECORD_SINCE): - super().run(name=name, folder=folder) + super().run(name=name, folder=folder, total=1) self.config.record_save(option=option) def run_event_daily(self): diff --git a/module/map/camera.py b/module/map/camera.py index 73593ab92..5979f0731 100644 --- a/module/map/camera.py +++ b/module/map/camera.py @@ -197,7 +197,7 @@ class Camera(InfoBarHandler): if np.all(np.abs(vector) <= 0): break - def full_scan(self, battle_count=None, mystery_count=0): + def full_scan(self, battle_count=None, mystery_count=0, siren_count=0): """Scan the hole map. Args: @@ -217,7 +217,7 @@ class Camera(InfoBarHandler): queue = queue[1:] if battle_count is not None: - self.map.missing_predict(battle_count=battle_count, mystery_count=mystery_count) + self.map.missing_predict(battle_count=battle_count, mystery_count=mystery_count, siren_count=siren_count) self.map.show() def in_sight(self, location, sight=(-3, -1, 3, 2)): diff --git a/module/map/fleet.py b/module/map/fleet.py index 3ae013a87..3a4d2428a 100644 --- a/module/map/fleet.py +++ b/module/map/fleet.py @@ -13,6 +13,7 @@ class Fleet(Camera, AmbushHandler, MysteryHandler, MapOperation): fleet_current_index = 1 battle_count = 0 mystery_count = 0 + siren_count = 0 fleet_ammo = 5 ammo_count = 3 @@ -72,6 +73,7 @@ class Fleet(Camera, AmbushHandler, MysteryHandler, MapOperation): arrived = False # Wait to confirm fleet arrived. It does't appear immediately if fleet in combat . arrive_timer = Timer(0.3) + arrive_unexpected_timer = Timer(1.5) # Wait after ambushed. ambushed_retry = Timer(0.5) # If nothing happens, click again. @@ -102,6 +104,8 @@ class Fleet(Camera, AmbushHandler, MysteryHandler, MapOperation): arrived = True result = 'combat' self.battle_count += 1 + if 'siren' in expected: + self.siren_count += 1 self.fleet_ammo -= 1 self.map[location_ensure(location)].is_cleared = True self.handle_boss_appear_refocus() @@ -113,10 +117,14 @@ class Fleet(Camera, AmbushHandler, MysteryHandler, MapOperation): if grid.predict_fleet(): arrive_timer.start() + arrive_unexpected_timer.start() if not arrive_timer.reached(): continue - if expected and result != expected: - continue + if expected and result not in expected: + if arrive_unexpected_timer.reached(): + logger.warning('Arrive with unexpected result') + else: + continue logger.info('Arrive confirm') arrived = True break @@ -202,11 +210,12 @@ class Fleet(Camera, AmbushHandler, MysteryHandler, MapOperation): logger.hr('Map init') self.battle_count = 0 self.mystery_count = 0 + self.siren_count = 0 self.ammo_count = 3 self.map = map_ self.map.reset() self.ensure_edge_insight(preset=self.map.in_map_swipe_preset_data) - self.full_scan(battle_count=self.battle_count, mystery_count=self.mystery_count) + self.full_scan(battle_count=self.battle_count, mystery_count=self.mystery_count, siren_count=self.siren_count) self.find_current_fleet() self.find_path_initial() diff --git a/module/map/grid_predictor.py b/module/map/grid_predictor.py index 479f57263..410a0e7cc 100644 --- a/module/map/grid_predictor.py +++ b/module/map/grid_predictor.py @@ -69,12 +69,9 @@ class GridPredictor: self.enemy_scale = self.predict_enemy_scale() if self.enemy_scale > 0: self.is_enemy = True - if self.may_siren: - if not self.is_enemy: - self.is_enemy = self.predict_dynamic_red_border() - if self.is_enemy: - self.is_siren = True self.is_mystery = self.predict_mystery() + if not self.is_enemy and not self.is_mystery: + self.is_siren = self.predict_dynamic_red_border() self.is_fleet = self.predict_fleet() self.is_boss = self.predict_boss() # self.image_perspective = color_similarity_2d( @@ -152,6 +149,7 @@ class GridPredictor: mask = np.pad(mask, ((pad, pad), (pad, pad)), mode='constant', constant_values=1) image = image * mask image[r < 221] = 0 + # print(self, np.mean(image)) return np.mean(image) > 2 @@ -168,6 +166,23 @@ class GridPredictor: count = np.sum(image > color_threshold) return count + def _relative_image_color_hue_count(self, area, h, s=None, v=None, output_shape=(50, 50)): + image = self.get_relative_image(area, output_shape=output_shape) + # if str(self) == 'A4': + # image.show() + hsv = rgb2hsv(np.array(image) / 255) + hue = hsv[:, :, 0] + count = (h[0] / 360 < hue) & (hue < h[1] / 360) + if s: + saturation = hsv[:, :, 1] + count &= (s[0] / 100 < saturation) & (saturation < s[1] / 100) + if v: + value = hsv[:, :, 2] + count &= (v[0] / 100 < value) & (value < v[1] / 100) + + count = np.sum(count) + return count + def predict_mystery(self): # if not self.may_mystery: # return False @@ -184,8 +199,11 @@ class GridPredictor: def predict_fleet(self): # white ammo icon - return self._relative_image_color_count( - area=(-1, -2, -0.5, -1.5), color=(255, 255, 255), color_threshold=252) > 300 + # return self._relative_image_color_count( + # area=(-1, -2, -0.5, -1.5), color=(255, 255, 255), color_threshold=252) > 300 + count = self._relative_image_color_hue_count(area=(-1, -2, -0.5, -1.5), h=(-3, 3), v=(50, 101)) + count += self._relative_image_color_hue_count(area=(-1, -2, -0.5, -1.5), h=(180 - 3, 180 + 3), v=(50, 101)) + return count > 300 def predict_current_fleet(self): # Green arrow over head with hue around 141. @@ -195,6 +213,9 @@ class GridPredictor: return count > 1000 def predict_boss(self): - count = self._relative_image_color_count( - area=(-0.5, -0.2, 0.5, 0.2), color=(255, 77, 82), color_threshold=247) - return count > 100 + # count = self._relative_image_color_count( + # area=(-0.5, -0.2, 0.5, 0.2), color=(255, 77, 82), color_threshold=247) + # return count > 100 + + # 微层混合 event_20200326_cn + return self._relative_image_color_hue_count(area=(-0.5, -0.2, 0.5, 0.2), h=(358 - 3, 358 + 3)) > 250 diff --git a/module/map/map.py b/module/map/map.py index a0134136b..d0dd7b662 100644 --- a/module/map/map.py +++ b/module/map/map.py @@ -6,7 +6,7 @@ from module.map.map_grids import SelectedGrids, RoadGrids class Map(Fleet): - def clear_chosen_enemy(self, grid): + def clear_chosen_enemy(self, grid, expected=''): """ Args: grid (GridInfo): @@ -15,9 +15,9 @@ class Map(Fleet): self.show_fleet() if self.config.ENABLE_EMOTION_REDUCE and self.config.ENABLE_MAP_FLEET_LOCK: self.emotion.wait() - self.goto(grid, expected='combat') + self.goto(grid, expected=f'combat_{expected}' if expected else 'combat') - self.full_scan(battle_count=self.battle_count, mystery_count=self.mystery_count) + self.full_scan(battle_count=self.battle_count, mystery_count=self.mystery_count, siren_count=self.siren_count) self.find_path_initial() def clear_chosen_mystery(self, grid): @@ -261,12 +261,16 @@ class Map(Fleet): def clear_siren(self): grids = self.map.select(may_siren=True, is_enemy=True, is_accessible=True) + grids = grids.add(self.map.select(may_siren=True, is_siren=True, is_accessible=True)) + logger.info('May siren: %s' % self.map.select(may_siren=True)) + logger.info('May siren and is enemy: %s' % self.map.select(may_siren=True, is_enemy=True)) + logger.info('Is siren: %s' % self.map.select(is_siren=True)) if grids: logger.hr('Clear siren') grids = grids.sort(cost=True, weight=True) logger.info('Grids: %s' % str(grids)) - self.clear_chosen_enemy(grids[0]) + self.clear_chosen_enemy(grids[0], expected='siren') return True return False diff --git a/module/map/map_base.py b/module/map/map_base.py index 4a7420742..f5a5c6186 100644 --- a/module/map/map_base.py +++ b/module/map/map_base.py @@ -76,9 +76,18 @@ class CampaignMap: @shape.setter def shape(self, scale): self._shape = node2location(scale.upper()) + for y in range(self._shape[1] + 1): + for x in range(self._shape[0] + 1): + grid = GridInfo() + grid.location = (x, y) + self.grids[(x, y)] = grid # camera_data can be generate automatically, but it's better to set it manually. - self._camera_data = SelectedGrids(camera_2d(self._shape, sight=(-3, -1, 3, 2))) + self.camera_data = [location2node(loca) for loca in camera_2d(self._shape, sight=(-3, -1, 3, 2))] + + # weight_data set to 10. + for grid in self: + grid.weight = 10. @property def map_data(self): @@ -88,11 +97,7 @@ class CampaignMap: def map_data(self, text): self._map_data = text for loca, data in self._parse_text(text): - grid = GridInfo() - grid.location = loca - grid.weight = 10. # weight_data can be generate automatically - grid.decode(data) - self.grids[loca] = grid + self.grids[loca].decode(data) def show(self): # logger.info('Showing grids:') @@ -298,11 +303,12 @@ class CampaignMap: return path - def missing_get(self, battle_count, mystery_count=0): + def missing_get(self, battle_count, mystery_count=0, siren_count=0): missing = self.spawn_data[battle_count].copy() - may = {'enemy': 0, 'mystery': 0, 'siren': 0,'boss': 0} + may = {'enemy': 0, 'mystery': 0, 'siren': 0, 'boss': 0} missing['enemy'] -= battle_count missing['mystery'] -= mystery_count + missing['siren'] -= siren_count for grid in self: for attr in may.keys(): if grid.__getattribute__('is_' + attr): @@ -321,8 +327,8 @@ class CampaignMap: logger.info('may: %s' % may) return may, missing - def missing_is_none(self, battle_count, mystery_count=0): - may, missing = self.missing_get(battle_count, mystery_count) + def missing_is_none(self, battle_count, mystery_count=0, siren_count=0): + may, missing = self.missing_get(battle_count, mystery_count, siren_count) for key in may.keys(): if missing[key] != 0: @@ -330,8 +336,8 @@ class CampaignMap: return True - def missing_predict(self, battle_count, mystery_count=0): - may, missing = self.missing_get(battle_count, mystery_count) + def missing_predict(self, battle_count, mystery_count=0, siren_count=0): + may, missing = self.missing_get(battle_count, mystery_count, siren_count) # predict for grid in self: diff --git a/module/map/map_operation.py b/module/map/map_operation.py index fb75274d6..79a6bfa11 100644 --- a/module/map/map_operation.py +++ b/module/map/map_operation.py @@ -38,7 +38,7 @@ class MapOperation(UrgentCommissionHandler, EnemySearchingHandler, FleetPreparat self.device.screenshot() if not checked_in_map and self.is_in_map(): - logger.info('Already in map.') + logger.info('Already in map, skip enter_map.') return False else: checked_in_map = True diff --git a/module/map/perspective.py b/module/map/perspective.py index 9564d0c0a..d066e26fe 100644 --- a/module/map/perspective.py +++ b/module/map/perspective.py @@ -111,7 +111,7 @@ class Perspective: ) if len(horizontal) - len(self.horizontal) >= 3 or len(vertical) - len(self.vertical) >= 3: logger.warning('Too many deleted lines') - # self.save_error_image() + self.save_error_image() def load_image(self, image): """Method that turns image to monochrome and hide UI.