def _init_scene(self, debug=False): self.election_period = 5 * 1000 # msec self.stage_matchers = MultiClassIkaMatcher() self.rule_matchers = MultiClassIkaMatcher() for stage_id in stages.keys(): stage = IkaMatcher( self.mapname_left, self.mapname_top, self.mapname_width, self.mapname_height, img_file='stage_%s.png' % stage_id, threshold=0.95, orig_threshold=0.30, bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), label='stage:%s' % stage_id, call_plugins=self._call_plugins, debug=debug, ) setattr(stage, 'id_', stage_id) self.stage_matchers.add_mask(stage) for rule_id in rules.keys(): rule = IkaMatcher( self.rulename_left, self.rulename_top, self.rulename_width, self.rulename_height, img_file='rule_%s.png' % rule_id, threshold=0.95, orig_threshold=0.30, bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), label='rule:%s' % rule_id, call_plugins=self._call_plugins, debug=debug, ) setattr(rule, 'id_', rule_id) self.rule_matchers.add_mask(rule)
def _init_scene(self, debug=False): # # To gather mask data, enable this. # self.write_samples = False # Load mask files. self._masks = MultiClassIkaMatcher() for special_weapon in special_weapons.keys(): try: mask = IkaMatcher( 0, 0, 150, 24, img_file='special_%s.png' % special_weapon, threshold=0.90, orig_threshold=0.20, bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), label='special/%s' % special_weapon, call_plugins=self._call_plugins, debug=debug, ) mask._id = special_weapon self._masks.add_mask(mask) except: IkaUtils.dprint('%s: Failed to load mask for %s' % (self, special_weapon)) pass
def reset(self): super(GameRankedBattleEvents, self).reset() self._last_event_msec = - 100 * 1000 self._last_mask_matched = None self._last_mask_triggered_msec = - 100 * 1000 self._masks_active = {} self._masks_active2 = MultiClassIkaMatcher()
def on_game_start(self, context): rule_id = context['game']['rule'] masks_active = self._masks_ranked.copy() if rule_id == 'area': masks_active.update(self._masks_splatzone) elif rule_id == 'hoko': masks_active.update(self._masks_rainmaker) elif rule_id == 'yagura': masks_active.update(self._masks_towercontrol) else: masks_active = {} self._masks_active = masks_active # Initialize Multi-Class IkaMatcher self._masks_active2 = MultiClassIkaMatcher() for mask in masks_active.keys(): self._masks_active2.add_mask(mask)
class GameSpecialWeapon(StatefulScene): # Called per Engine's reset. def reset(self): super(GameSpecialWeapon, self).reset() self.img_last_special = None def _match_phase1(self, context, img_special, img_last_special): # # Phase 1 # # Crop the area special weapon message supposed to be appeared. # Compare with last frame, and check if it is (almost) same with # the last frame. # img_special_diff = abs(img_special - img_last_special) matched = bool(np.average(img_special_diff) < 90) return matched def _is_my_special_weapon(self, context, img_special_bgr): img = img_special_bgr[:, :150] img_s = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:, :, 2] img_s[matcher.MM_WHITE()(img) > 127] = 127 img_s_hist = cv2.calcHist(img_s[:, :], [0], None, [5], [0, 256]) img_s_hist_black = float(np.amax(img_s_hist[0:1])) img_s_hist_non_black = float(np.amax(img_s_hist[3:4])) return img_s_hist_black < img_s_hist_non_black def _state_default(self, context): if not self.is_another_scene_matched(context, 'GameTimerIcon'): return False frame = context['engine']['frame'] if frame is None: return False # FIXME: this code works with the first special weapon only img_special_bgr = frame[260:260 + 24, 1006:1006 + 210, :] img_special = np.array(img_special_bgr) img_last_special = self.img_last_special self.img_last_special = img_special if img_last_special is None: return False if not self._match_phase1(context, img_special, img_last_special): return False # # Phase 2 # # Check inkling image on right side img_sp_char = cv2.cvtColor( img_special_bgr[:, 150:210], cv2.COLOR_BGR2GRAY) laplacian_threshold = 60 img_laplacian = cv2.Laplacian(img_sp_char, cv2.CV_64F) img_laplacian_abs = cv2.convertScaleAbs(img_laplacian) c_matched = bool(np.average(img_laplacian_abs) > 20) if not c_matched: return False # Phase 3 # TODO: Background color # Phase 4 # Forground text white_filter = matcher.MM_WHITE() img_sp_text = white_filter(img_special_bgr[:, 0:150]) special = self._masks.match_best(img_special_bgr)[1] if self.write_samples: cv2.imwrite('training/_special_%s.png' % time.time(), 255 - img_sp_text) if special is None: return False context['game']['special_weapon'] = special._id context['game']['special_weapon_is_mine'] = \ self._is_my_special_weapon(context, img_special_bgr) self._call_plugins('on_game_special_weapon') self._switch_state(self._state_tracking) return True def _state_tracking(self, context): if not self.is_another_scene_matched(context, 'GameTimerIcon'): return False frame = context['engine']['frame'] if frame is None: return False # FIXME img_special_bgr = frame[260:260 + 24, 1006:1006 + 210, :] img_special = np.array(img_special_bgr) img_last_special = self.img_last_special if img_last_special is None: return False special = self._masks.match_best(img_special_bgr)[1] if special is not None: self._call_plugins( 'on_mark_rect_in_preview', [(1006, 260), (1006 + 210, 260 + 24)] ) if context['game']['special_weapon'] == special._id: return True if self.matched_in(context, 150): return False self._switch_state(self._state_default) self.img_last_special = None return False def dump(self, context): # Not implemented :\ pass def _analyze(self, context): pass # Called only once on initialization. def _init_scene(self, debug=False): # # To gather mask data, enable this. # self.write_samples = False # Load mask files. self._masks = MultiClassIkaMatcher() for special_weapon in special_weapons.keys(): try: mask = IkaMatcher( 0, 0, 150, 24, img_file='special_%s.png' % special_weapon, threshold=0.90, orig_threshold=0.20, bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), label='special/%s' % special_weapon, call_plugins=self._call_plugins, debug=debug, ) mask._id = special_weapon self._masks.add_mask(mask) except: IkaUtils.dprint('%s: Failed to load mask for %s' % (self, special_weapon)) pass
class GameStart(StatefulScene): # 720p サイズでの値 mapname_width = 430 mapname_left = 1280 - mapname_width mapname_top = 580 mapname_height = 640 - mapname_top rulename_left = 640 - 120 rulename_right = 640 + 120 rulename_width = rulename_right - rulename_left rulename_top = 250 rulename_bottom = 310 rulename_height = rulename_bottom - rulename_top def reset(self): super(GameStart, self).reset() self.stage_votes = [] self.rule_votes = [] self._last_event_msec = - 100 * 1000 self._last_run_msec = - 100 * 1000 def elect(self, context, votes): # Discard too old data. election_start = context['engine']['msec'] - self.election_period votes = list(filter(lambda e: election_start < e[0], votes)) # count items = {} for vote in votes: if vote[1] is None: continue key = vote[1] items[key] = items.get(key, 0) + 1 # return the best key sorted_keys = sorted( items.keys(), key=lambda x: items[x], reverse=True) sorted_keys.extend([None]) # fallback return sorted_keys[0] def _detect_stage_and_rule(self, context): frame = context['engine']['frame'] stage = None rule = None best_stage = self.stage_matchers.match_best(frame) best_rule = self.rule_matchers.match_best(frame) if best_stage[1] is not None: stage = best_stage[1].id_ if best_rule[1] is not None: rule = best_rule[1].id_ return stage, rule def _state_default(self, context): timer_icon = self.find_scene_object('GameTimerIcon') if (timer_icon is not None) and timer_icon.matched_in(context, 3000): return False frame = context['engine']['frame'] if frame is None: return False if self.matched_in(context, 1500, attr='_last_run_msec'): return False else: self._last_run_msec = context['engine']['msec'] # Get the best matched stat.ink key stage, rule = self._detect_stage_and_rule(context) if stage or rule: self.stage_votes = [] self.rule_votes = [] self.stage_votes.append((context['engine']['msec'], stage)) self.rule_votes.append((context['engine']['msec'], rule)) self._switch_state(self._state_tracking) return True return False def _state_tracking(self, context): frame = context['engine']['frame'] if frame is None: return False stage, rule = self._detect_stage_and_rule(context) matched = (stage or rule) # 画面が続いているならそのまま if matched: self.stage_votes.append((context['engine']['msec'], stage)) self.rule_votes.append((context['engine']['msec'], rule)) return True # 1000ms 以内の非マッチはチャタリングとみなす if not matched and self.matched_in(context, 1000): return False # それ以上マッチングしなかった場合 -> シーンを抜けている if not self.matched_in(context, 20000, attr='_last_event_msec'): context['game']['map'] = self.elect(context, self.stage_votes) context['game']['rule'] = self.elect(context, self.rule_votes) if not context['game']['start_time']: # start_time should be initialized in GameGoSign. # This is a fallback in case GameGoSign was skipped. context['game']['start_time'] = IkaUtils.getTime(context) context['game']['start_offset_msec'] = \ context['engine']['msec'] self._call_plugins('on_game_start') self._last_event_msec = context['engine']['msec'] self._switch_state(self._state_default) return False def _analyze(self, context): pass def dump(self, context): for v in self.stage_votes: if v[1] is None: continue print('stage', v[0], v[1]) for v in self.rule_votes: if v[1] is None: continue print('rule', v[0], v[1]) def _init_scene(self, debug=False): self.election_period = 5 * 1000 # msec self.stage_matchers = MultiClassIkaMatcher() self.rule_matchers = MultiClassIkaMatcher() for stage_id in stages.keys(): stage = IkaMatcher( self.mapname_left, self.mapname_top, self.mapname_width, self.mapname_height, img_file='stage_%s.png' % stage_id, threshold=0.95, orig_threshold=0.30, bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), label='stage:%s' % stage_id, call_plugins=self._call_plugins, debug=debug, ) setattr(stage, 'id_', stage_id) self.stage_matchers.add_mask(stage) for rule_id in rules.keys(): rule = IkaMatcher( self.rulename_left, self.rulename_top, self.rulename_width, self.rulename_height, img_file='rule_%s.png' % rule_id, threshold=0.95, orig_threshold=0.30, bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), label='rule:%s' % rule_id, call_plugins=self._call_plugins, debug=debug, ) setattr(rule, 'id_', rule_id) self.rule_matchers.add_mask(rule)
def on_game_reset(self, context): self._masks_active = {} self._masks_active2 = MultiClassIkaMatcher()
class GameRankedBattleEvents(StatefulScene): # Called per Engine's reset. def reset(self): super(GameRankedBattleEvents, self).reset() self._last_event_msec = - 100 * 1000 self._last_mask_matched = None self._last_mask_triggered_msec = - 100 * 1000 self._masks_active = {} self._masks_active2 = MultiClassIkaMatcher() def on_game_reset(self, context): self._masks_active = {} self._masks_active2 = MultiClassIkaMatcher() def on_game_start(self, context): rule_id = context['game']['rule'] masks_active = self._masks_ranked.copy() if rule_id == 'area': masks_active.update(self._masks_splatzone) elif rule_id == 'hoko': masks_active.update(self._masks_rainmaker) elif rule_id == 'yagura': masks_active.update(self._masks_towercontrol) else: masks_active = {} self._masks_active = masks_active # Initialize Multi-Class IkaMatcher self._masks_active2 = MultiClassIkaMatcher() for mask in masks_active.keys(): self._masks_active2.add_mask(mask) def _state_triggered(self, context): frame = context['engine']['frame'] if frame is None: return False most_possible = self._masks_active2.match_best(frame)[1] if most_possible is None: self._switch_state(self._state_default) if most_possible != self._last_mask_matched: IkaUtils.dprint('%s: matched %s' % (self, most_possible)) self._last_mask_matched = most_possible # self._switch_state(self._state_pending) return True def _state_pending(self, context): # if self.is_another_scene_matched(context, 'GameTimerIcon'): # return False frame = context['engine']['frame'] if frame is None: return False most_possible = self._masks_active2.match_best(frame)[1] if most_possible is None: self._switch_state(self._state_default) if most_possible != self._last_mask_matched: self._last_mask_matched = most_possible return True # else: # if most_possbile == self._last_mask_matched: # go through # not self.matched_in(context, 3000, attr='_last_mask_triggered_msec'): if 1: event = self._masks_active[most_possible] IkaUtils.dprint('%s: trigger an event %s' % (self, event)) self._call_plugins(event) self._last_mask_triggered = most_possible self._last_mask_triggered_msec = context['engine']['msec'] self._switch_state(self._state_triggered) def _state_default(self, context): # if self.is_another_scene_matched(context, 'GameTimerIcon'): # return False frame = context['engine']['frame'] if frame is None: return False most_possible = self._masks_active2.match_best(frame)[1] if most_possible is None: return False # IkaUtils.dprint('%s: matched %s' % (self, most_possible)) self._last_mask_matched = most_possible self._switch_state(self._state_pending) return True def _analyze(self, context): pass def _load_splatzone_masks(self, debug=False): mask_we_got = IkaMatcher( 473, 177, 273, 36, img_file='splatzone_we_got.png', threshold=0.9, orig_threshold=0.1, label='splatzone/we_got', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) mask_we_lost = IkaMatcher( 473, 177, 273, 36, img_file='splatzone_we_lost.png', threshold=0.9, orig_threshold=0.1, label='splatzone/we_lost', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) mask_they_got = IkaMatcher( 473, 177, 273, 36, img_file='splatzone_they_got.png', threshold=0.9, orig_threshold=0.1, label='splatzone/they_got', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) mask_they_lost = IkaMatcher( 473, 177, 273, 36, img_file='splatzone_they_lost.png', threshold=0.9, orig_threshold=0.1, label='splatzone/they_lost', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) self._masks_splatzone = { mask_we_got: 'on_game_splatzone_we_got', mask_we_lost: 'on_game_splatzone_we_lost', mask_they_got: 'on_game_splatzone_they_got', mask_they_lost: 'on_game_splatzone_they_lost', } def _load_rainmaker_masks(self, debug=False): mask_we_got = IkaMatcher( 473, 177, 273, 36, img_file='rainmaker_we_got.png', threshold=0.9, orig_threshold=0.1, label='rainmaker/we_got', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) mask_we_lost = IkaMatcher( 473, 177, 273, 36, img_file='rainmaker_we_lost.png', threshold=0.9, orig_threshold=0.1, label='rainmaker/we_lost', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) mask_they_got = IkaMatcher( 473, 177, 273, 36, img_file='rainmaker_they_got.png', threshold=0.9, orig_threshold=0.1, label='rainmaker/they_got', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) mask_they_lost = IkaMatcher( 473, 177, 273, 36, img_file='rainmaker_they_lost.png', threshold=0.9, orig_threshold=0.1, label='rainmaker/they_lost', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) self._masks_rainmaker = { mask_we_got: 'on_game_rainmaker_we_got', mask_we_lost: 'on_game_rainmaker_we_lost', mask_they_got: 'on_game_rainmaker_they_got', mask_they_lost: 'on_game_rainmaker_they_lost', } def _load_towercontrol_masks(self, debug=False): mask_we_took = IkaMatcher( 473, 177, 273, 36, img_file='towercontrol_we_took.png', threshold=0.9, orig_threshold=0.1, label='towercontrol/we_took', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) mask_we_lost = IkaMatcher( 473, 177, 273, 36, img_file='towercontrol_we_lost.png', threshold=0.9, orig_threshold=0.1, label='towercontrol/we_lost', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) mask_they_took = IkaMatcher( 473, 177, 273, 36, img_file='towercontrol_they_took.png', threshold=0.9, orig_threshold=0.1, label='towercontrol/they_took', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) mask_they_lost = IkaMatcher( 473, 177, 273, 36, img_file='towercontrol_they_lost.png', threshold=0.9, orig_threshold=0.1, label='towercontrol/they_lost', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) self._masks_towercontrol = { mask_we_took: 'on_game_towercontrol_we_took', mask_we_lost: 'on_game_towercontrol_we_lost', mask_they_took: 'on_game_towercontrol_they_took', mask_they_lost: 'on_game_towercontrol_they_lost', } # Called only once on initialization. def _init_scene(self, debug=False): self._load_rainmaker_masks(debug=debug) self._load_splatzone_masks(debug=debug) self._load_towercontrol_masks(debug=debug) self.mask_we_lead = IkaMatcher( 473, 177, 273, 36, img_file='ranked_we_lead.png', threshold=0.9, orig_threshold=0.1, label='splatzone/we_lead', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) self.mask_they_lead = IkaMatcher( 473, 177, 273, 36, img_file='ranked_they_lead.png', threshold=0.9, orig_threshold=0.1, label='splatzone/they_lead', bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), call_plugins=self._call_plugins, debug=debug, ) self._masks_ranked = { self.mask_we_lead: 'on_game_ranked_we_lead', self.mask_they_lead: 'on_game_ranked_they_lead', }
class GameStart(StatefulScene): # 720p サイズでの値 mapname_width = 430 mapname_left = 1280 - mapname_width mapname_top = 580 mapname_height = 640 - mapname_top rulename_left = 640 - 120 rulename_right = 640 + 120 rulename_width = rulename_right - rulename_left rulename_top = 250 rulename_bottom = 310 rulename_height = rulename_bottom - rulename_top def reset(self): super(GameStart, self).reset() self.stage_votes = [] self.rule_votes = [] self._last_event_msec = -100 * 1000 self._last_run_msec = -100 * 1000 def elect(self, context, votes): # Discard too old data. election_start = context['engine']['msec'] - self.election_period votes = list(filter(lambda e: election_start < e[0], votes)) # count items = {} for vote in votes: if vote[1] is None: continue key = vote[1] items[key] = items.get(key, 0) + 1 # return the best key sorted_keys = sorted(items.keys(), key=lambda x: items[x], reverse=True) sorted_keys.extend([None]) # fallback return sorted_keys[0] def _detect_stage_and_rule(self, context): frame = context['engine']['frame'] stage = None rule = None best_stage = self.stage_matchers.match_best(frame) best_rule = self.rule_matchers.match_best(frame) if best_stage[1] is not None: stage = best_stage[1].id_ if best_rule[1] is not None: rule = best_rule[1].id_ return stage, rule def _state_default(self, context): timer_icon = self.find_scene_object('GameTimerIcon') if (timer_icon is not None) and timer_icon.matched_in(context, 3000): return False frame = context['engine']['frame'] if frame is None: return False if self.matched_in(context, 1500, attr='_last_run_msec'): return False else: self._last_run_msec = context['engine']['msec'] # Get the best matched stat.ink key stage, rule = self._detect_stage_and_rule(context) if stage or rule: self.stage_votes = [] self.rule_votes = [] self.stage_votes.append((context['engine']['msec'], stage)) self.rule_votes.append((context['engine']['msec'], rule)) self._switch_state(self._state_tracking) return True return False def _state_tracking(self, context): frame = context['engine']['frame'] if frame is None: return False stage, rule = self._detect_stage_and_rule(context) matched = (stage or rule) # 画面が続いているならそのまま if matched: self.stage_votes.append((context['engine']['msec'], stage)) self.rule_votes.append((context['engine']['msec'], rule)) return True # 1000ms 以内の非マッチはチャタリングとみなす if not matched and self.matched_in(context, 1000): return False # それ以上マッチングしなかった場合 -> シーンを抜けている if not self.matched_in(context, 20000, attr='_last_event_msec'): context['game']['map'] = self.elect(context, self.stage_votes) context['game']['rule'] = self.elect(context, self.rule_votes) if not context['game']['start_time']: # start_time should be initialized in GameGoSign. # This is a fallback in case GameGoSign was skipped. context['game']['start_time'] = IkaUtils.getTime(context) context['game']['start_offset_msec'] = \ context['engine']['msec'] self._call_plugins('on_game_start') self._last_event_msec = context['engine']['msec'] self._switch_state(self._state_default) return False def _analyze(self, context): pass def dump(self, context): for v in self.stage_votes: if v[1] is None: continue print('stage', v[0], v[1]) for v in self.rule_votes: if v[1] is None: continue print('rule', v[0], v[1]) def _init_scene(self, debug=False): self.election_period = 5 * 1000 # msec self.stage_matchers = MultiClassIkaMatcher() self.rule_matchers = MultiClassIkaMatcher() for stage_id in stages.keys(): stage = IkaMatcher( self.mapname_left, self.mapname_top, self.mapname_width, self.mapname_height, img_file='stage_%s.png' % stage_id, threshold=0.95, orig_threshold=0.30, bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), label='stage:%s' % stage_id, call_plugins=self._call_plugins, debug=debug, ) setattr(stage, 'id_', stage_id) self.stage_matchers.add_mask(stage) for rule_id in rules.keys(): rule = IkaMatcher( self.rulename_left, self.rulename_top, self.rulename_width, self.rulename_height, img_file='rule_%s.png' % rule_id, threshold=0.95, orig_threshold=0.30, bg_method=matcher.MM_NOT_WHITE(), fg_method=matcher.MM_WHITE(), label='rule:%s' % rule_id, call_plugins=self._call_plugins, debug=debug, ) setattr(rule, 'id_', rule_id) self.rule_matchers.add_mask(rule)