def run_level(module_model, char_model, symbol_model, minutes, seconds, num_modules): LIGHT_MONITOR.start() time_started = time() # Time started is used for solving the button module. log("Inspecting bomb...") images = inspect_bomb.inspect_bomb(num_modules) # Capture images of all sides of the bomb. side_partitions = inspect_bomb.partition_sides(images) # Split images into modules/side of bomb. # Identify features from the side of the bomb (indicators, batteries, serial number, etc.). predictions = identify_side_features(side_partitions, module_model) modules = 12 if num_modules > 5 else 6 sw, sh = win_util.get_screen_size() #add_overlay_properties("module_positions", [get_module_coords(x) for x in range(6)]) add_overlay_properties("module_names", [module_classifier.LABELS[x] for x in predictions[:modules]]) # Extract aforementioned features (read serial number, count num. of batteries, etc.). side_features = extract_side_features(side_partitions[1:], predictions[modules:], char_model) log(f"Side features: {side_features}", verbose=config.LOG_DEBUG) log(f"Modules: {[module_classifier.LABELS[x] for x in predictions[:modules]]}", config.LOG_DEBUG) try: # Solve all modules. Back of the bomb first, from left to right, top to bottom. solve_modules(predictions[:modules], side_features, char_model, symbol_model, (time_started, minutes, seconds)) except KeyboardInterrupt: # Catch SIGINT from LightMonitor (meaning the bomb exploded). log("Exiting...") exit(0) LIGHT_MONITOR.shut_down()
def continue_button(): sw, sh = win_util.get_screen_size() x = sw // 2 y = int(sh * 0.65) win_util.click(x, y) sleep(1) win_util.click(x, y)
def monitor(self): _, SH = get_screen_size() lo = (30, 30, 30) hi = (255, 255, 255) self.change_event.set() while self.is_active: sc = screenshot(0, SH - 200, 200, 200) img = features_util.convert_to_cv2(sc) rgb = features_util.split_channels(img) if self.lights_on: if not features_util.color_in_range(self.pixel, rgb, lo, hi): if self.bomb_exploded(rgb): # We died :( self.is_active = False self.exit_after_explosion() else: log("Lights in the room are turned off. Pausing execution temporarily..." ) self.lights_on = False self.change_event.clear() sleep(1) else: if features_util.color_in_range(self.pixel, rgb, lo, hi): log("Lights in the room are turned back on. Resuming...") self.change_event.set() self.lights_on = True sleep(1) sleep(0.25)
def create_bg_image(): sw, sh = win_util.get_screen_size() padding = 20 if "debug_bg_img" in GUIOverlay.properties: GUIOverlay.bg_img = np.copy(GUIOverlay.properties["debug_bg_img"]) else: GUIOverlay.bg_img = np.zeros((sh - padding, sw - padding, 3), dtype="uint8")
def get_module_coords(module): SW, SH = win_util.get_screen_size() start_x = SW * 0.35 start_y = SH * 0.35 offset_x = 300 offset_y = 300 x = int(start_x + ((module % 3) * offset_x)) y = int(start_y + ((module // 3) * offset_y)) return x, y
def log_info(img, info): sw, sh = win_util.get_screen_size() width = 350 x = sw - width y_step = 20 start_y = 100 draw_rect(img, (x - 10, start_y), (sw, sh), (0, 0, 0), -1) max_lines = int(sh // 23) # 47 for 1080. for i, s in enumerate(info[-max_lines:]): draw_text(img, s, (x, start_y + 10 + (y_step * i)), 1.2, (255, 255, 255), 1)
def get_module_characters(): SW, SH = win_util.get_screen_size() sc = screenshot(int(SW * 0.524), int(SH * 0.547), 36, 25) img = cv2.cvtColor(np.array(sc), cv2.COLOR_RGB2BGR) thresh = get_threshold(img) masks = get_masked_images(thresh) if len(masks) == 1 and masks[0].shape[1] > 20: mask1 = masks[0][:, :masks[0].shape[1]//2] mask2 = masks[0][:, masks[0].shape[1]//2:] masks = [mask1, mask2] return masks
def restart_level(level): SW, SH = win_util.get_screen_size() win_util.click(int(SW * 0.9), int(SH * 0.8), btn="right") sleep(1) win_util.click(int(SW * 0.73), int(SH * 0.65)) sleep(1) win_util.click(int(SW * 0.73), int(SH * 0.45)) sleep(2) win_util.click(int(SW * 0.52), int(SH * 0.53)) sleep(0.8) inspect_bomb.select_level(level) sleep(0.5)
def get_module_coords(module_index): """ Get screen coordinates of a module based on module index. """ SW, SH = win_util.get_screen_size() start_x = SW * 0.35 start_y = SH * 0.35 offset_x = 300 offset_y = 300 x = int(start_x + ((module_index % 3) * offset_x)) y = int(start_y + ((module_index // 3) * offset_y)) return x, y
def inspect(labels, labels_to_inspect): SW, SH = win_util.get_screen_size() filtered_labels = [] images = [] for module in range(12): mod_index = module if module < 6 else module - 6 if labels[module] in labels_to_inspect: main.select_module(mod_index) sleep(1) SC, _, _ = main.screenshot_module() images.append(SC) filtered_labels.append(labels[module]) main.deselect_module() sleep(0.5) if module == 5: # We have gone through 6 modules, flip the bomb over and proceeed. SW, SH = win_util.get_screen_size() flip_bomb(SW, SH) sleep(0.75) win_util.mouse_up(SW // 2, SH // 2, btn="right") sleep(0.5) return images, filtered_labels
def create_window(conn): sw, sh = win_util.get_screen_size() path = "../resources/misc/recorded_runs/" num_files = len(glob(path + "*")) record_path = f"{path}{num_files+1}/" mkdir(record_path) mon_bbox = {"top": 0, "left": 0, "width": sw, "height": sh} sct = mss() queue = Queue(5) io_thread = Thread(target=save_video_frame, args=(record_path, queue)) io_thread.start() Thread(target=listen, args=(conn, )).start() target_fps = 30 secs_per_frame = 1 / target_fps try: while not GUIOverlay.is_active: sleep(0.1) # Wait for start. time_started = GUIOverlay.properties.get("speedrun_time", None) timestamp = int(time()) while GUIOverlay.is_active: frame_time = time() img = np.array(sct.grab(mon_bbox)) img = GUIOverlay.pad_bg_img(img, 300, 100) if time_started is not None: new_time = int(time()) if new_time > timestamp: timestamp = new_time GUIOverlay.add_property("speedrun_time", timestamp - time_started) elif "speedrun_time" in GUIOverlay.properties: time_started = GUIOverlay.properties["speedrun_time"] frame_time = time() - frame_time if frame_time < secs_per_frame: sleep(secs_per_frame - frame_time) if time_started is not None: data = (img, GUIOverlay.properties, time() - time_started) queue.put(data, True) except KeyboardInterrupt: return finally: queue.put(None)
def solve_needy_modules(modules, needy_indices, curr_module, duration): SW, SH = win_util.get_screen_size() prev_index = curr_module timestamp = None for index in needy_indices: if (index > 5) ^ (prev_index > 5): # Flip the bomb, if needed. inspect_bomb.flip_bomb(SW, SH) sleep(0.75) win_util.mouse_up(SW // 2, SH // 2, btn="right") sleep(0.5) mod_index = index if index < 6 else index - 6 label = modules[index] select_module(mod_index) add_overlay_properties("module_info", (label)) SC, x, y = screenshot_module() mod_pos = (x, y) cv2_img = convert_to_cv2(SC) mod_name = module_classifier.LABELS[label] log(f"Solving {mod_name}...") try: if label == 20: # Needy Vent Gas. solve_needy_vent(cv2_img, mod_pos) elif label == 21: # Needy Discharge Capacitor. solve_needy_discharge(cv2_img, mod_pos) elif label == 22: # Solve Knob. solve_needy_knob(cv2_img, mod_pos) except KeyboardInterrupt: handle_module_exception(mod_name, cv2_img) raise KeyboardInterrupt except Exception: handle_module_exception(mod_name, cv2_img) if timestamp is None: cooldown_time = 5 timestamp = time() + cooldown_time sleep(0.5) deselect_module() prev_index = index # Flip the bomb back to it's original state, if needed. if (curr_module > 5) ^ (prev_index > 5): inspect_bomb.flip_bomb(SW, SH) sleep(0.75) win_util.mouse_up(SW // 2, SH // 2, btn="right") sleep(0.5) return timestamp
def inspect_bomb(num_modules=None): sw, sh = win_util.get_screen_size() mid_x = sw // 2 mid_y = sh // 2 win_util.click(mid_x, mid_y + (mid_y // 8)) sleep(0.5) # Inspect front of bomb. front_img = screenshot(460, 220, 1000, 640) sleep(0.2) # Rotate bomb. win_util.mouse_down(mid_x, mid_y, btn="right") sleep(0.2) # Inspect right side. right_img = inspect_side(sw - int(sw / 2.74), mid_y + int(mid_y / 8), 755, 60, 480, 900) # Inspect left side. left_img = inspect_side(int(sw / 2.76), mid_y + int(mid_y / 8), 755, 60, 480, 900) # Inspect top side. top_img = inspect_side(int(sw / 2.75), sh, 720, 0, 480, sh) # Inspect bottom side. bottom_img = inspect_side(int(sw / 2.75), 0, 720, 0, 480, sh) # Inspect back of bomb. win_util.mouse_up(mid_x, mid_y, btn="right") sleep(0.5) return_tupl = (front_img, left_img, right_img, top_img, bottom_img) if num_modules is None or num_modules > 5: flip_bomb(sw, sh) back_img = screenshot(460, 220, 1000, 640) sleep(0.4) win_util.mouse_up(mid_x, mid_y, btn="right") return_tupl = (back_img, ) + return_tupl else: win_util.click(200, 200, btn="right") sleep(0.4) win_util.click(mid_x, mid_y + (mid_y // 8)) sleep(0.5) return return_tupl
def draw_speedrun_splits(img, splits): sw, _ = win_util.get_screen_size() bar_height = 100 cv2.rectangle(img, (0, 0), (sw, bar_height), (0, 0, 0), -1) start_x = 5 step_x = 125 for i, (level, split) in enumerate(splits): segment = split if i < 1 else split - splits[i - 1][1] total_time_str = format_time(split) segment_time_str = format_time(segment) level_strs = format_level(level) x = start_x + (step_x * i) cv2.rectangle(img, (step_x * i, 0), (step_x * (i + 1), bar_height), (80, 80, 80), -1) cv2.rectangle(img, (step_x * i, 0), (step_x * (i + 1), bar_height), (0, 0, 0), 1) for lvl_s, lvl_y in level_strs: cv2.putText(img, lvl_s, (x, lvl_y), cv2.FONT_HERSHEY_PLAIN, 1.2, (255, 255, 255), 1) cv2.putText(img, total_time_str, (x, 60), cv2.FONT_HERSHEY_PLAIN, 1.6, (60, 230, 30), 2) cv2.putText(img, "(" + segment_time_str + ")", (x, 90), cv2.FONT_HERSHEY_PLAIN, 1.6, (40, 160, 40), 2)
def get_duration_characters(): SW, SH = win_util.get_screen_size() sc = screenshot(int(SW * 0.47), int(SH * 0.54), 80, 38) img = cv2.cvtColor(np.array(sc), cv2.COLOR_RGB2BGR) thresh = get_threshold(img) return get_masked_images(thresh)
def solve_modules(modules, side_features, character_model, symbol_model, duration): # Get list of indexes of needy modules (all modules an index over 19). needy_indices = list( filter(lambda i: modules[i] > 19, [x for x in range(len(modules))])) needy_timestamp = duration[0] module_durations = [2, 5, 2, 12, 10, 2, 14, 8, 8, 8, 20] log(f"Needy modules: {len(needy_indices)}", config.LOG_DEBUG) solved_modules = 0 num_modules = len(list(filter(lambda x: 8 < x < 20, modules))) module = 0 while module < len(modules): label = modules[module] LIGHT_MONITOR.wait_for_light() # If the room is dark, wait for light. mod_index = module if module < 6 else module - 6 bomb_solved = solved_modules == num_modules mod_duration = 2 if label > 19 else module_durations[label - 9] critical = needy_modules_critical(len(needy_indices), needy_timestamp, mod_duration) if not bomb_solved and critical: # Needy modules need attention! Solve them, and continue where we left off. needy_timestamp = solve_needy_modules(modules, needy_indices, module, needy_timestamp) if 8 < label < 20: select_module(mod_index) SC, x, y = screenshot_module() #add_overlay_properties("module_selected", (x, y, mod_index)) mod_pos = (x, y) cv2_img = convert_to_cv2(SC) mod_name = module_classifier.LABELS[label] log(f"Solving {mod_name}...") try: if label == 9: # Wires. solve_wires(cv2_img, mod_pos, side_features) elif label == 10: # Button. solve_button(cv2_img, mod_pos, side_features, character_model, duration) elif label == 11: # Symbols. solve_symbols(cv2_img, mod_pos, symbol_model) elif label == 12: # Simon Says. solve_simon(cv2_img, mod_pos, side_features) elif label == 13: # Wire Sequence. solve_wire_sequence(cv2_img, mod_pos) elif label == 14: # Complicated Wires. solve_complicated_wires(cv2_img, mod_pos, side_features) elif label == 15: # Memory Game. solve_memory(cv2_img, character_model, mod_pos) elif label == 16: # Who's on First? solve_whos_on_first(cv2_img, character_model, mod_pos) elif label == 17: # Maze. solve_maze(cv2_img, mod_pos) elif label == 18: # Password. solve_password(cv2_img, character_model, mod_pos) elif label == 19: # Morse. solve_morse(cv2_img, mod_pos) solved_modules += 1 except KeyboardInterrupt: # Bomb 'sploded. handle_module_exception(mod_name, cv2_img) raise KeyboardInterrupt except Exception: # If an exception happened while lights were off, we try again. if not LIGHT_MONITOR.lights_on: log("Exception while light was off. We try again in a bit!" ) deselect_module() continue handle_module_exception(mod_name, cv2_img) sleep(0.1) deselect_module() if module == 5 and solved_modules != num_modules: # We have gone through all modules on one side of the bomb, flip it over and continue. SW, SH = win_util.get_screen_size() inspect_bomb.flip_bomb(SW, SH) sleep(0.75) win_util.mouse_up(SW // 2, SH // 2, btn="right") sleep(0.5) module += 1 if solved_modules == num_modules: log("We did it! We live to defuse another bomb!") else: log("Some modules could not be disarmed, it seems we are doomed...") raise KeyboardInterrupt # We failed.
def start_level(): """ Click the 'Start Level' button, after a level has been selected. """ SW, SH = win_util.get_screen_size() win_util.click(int(SW - SW / 2.6), int(SH - SH / 3.3))
def screenshot_module(): SW, SH = win_util.get_screen_size() x = int(SW * 0.43) y = int(SH * 0.36) return screenshot(x, y, 300, 300), x, y
def draw_speedrun_time(img, s_time): sw, _ = win_util.get_screen_size() time_str = format_time(s_time, 0) draw_text(img, time_str, (sw - 180, 50), 2.8, (255, 255, 255), 2)
def select_bombs_menu(): sw, sh = win_util.get_screen_size() x = sw // 2 y = int(sh * 0.56) win_util.click(x, y)
from time import sleep import util.windows_util as win_util from model.grab_img import screenshot SW, SH = win_util.get_screen_size() LEVEL_COORDS = { "first_bomb": int(SH * 0.35), "old-new": int(SH * 0.47), "double_your_money": int(SH * 0.53), "step_up": int(SH * 0.58), "pick_up_pace_1": int(SH * 0.62), "hidden_message": int(SH * 0.26), "somethings_different": int(SH * 0.30), "one_giant_leap": int(SH * 0.35), "fair_game": int(SH * 0.39), "pick_up_pace_2": int(SH * 0.44), "no_room_for_error": int(SH * 0.48), "eight_modules": int(SH * 0.53), "small_wrinkle": int(SH * 0.64), "multi-tasker": int(SH * 0.35), "the_knob": int(SH * 0.305), "hardcore": int(SH * 0.55) } def next_level_page(): sw, sh = win_util.get_screen_size() win_util.click(int(sw * 0.65), int(sh * 0.70)) def select_level(level):
def next_level_page(): sw, sh = win_util.get_screen_size() win_util.click(int(sw * 0.65), int(sh * 0.70))