# algorithm_2d_1.py from collections import deque from utils import ( to_tuple_set, to_list, is_connected, is_move_connected, potential, apply_move, make_step_info, build_response, is_finished_2d ) # ============================================================ # 1. Hình học move 2D: slide và convex transition # ============================================================ def is_slide_2d(c, e, blocks_set): """ Slide trong 2D dựa trên 4-cycle. c và e phải kề cạnh. Tồn tại một phía của cạnh c-e sao cho hai ô còn lại của hình vuông 2x2 đều có block. """ dx = e[0] - c[0] dy = e[1] - c[1] if abs(dx) + abs(dy) != 1: return False # vector vuông góc với hướng di chuyển p1 = (-dy, dx) p2 = (dy, -dx) side1_a = (c[0] + p1[0], c[1] + p1[1]) side1_b = (e[0] + p1[0], e[1] + p1[1]) side2_a = (c[0] + p2[0], c[1] + p2[1]) side2_b = (e[0] + p2[0], e[1] + p2[1]) if side1_a in blocks_set and side1_b in blocks_set: return True if side2_a in blocks_set and side2_b in blocks_set: return True return False def is_convex_2d(c, e, blocks_set): """ Convex transition trong 2D. c và e nằm chéo nhau trong một hình vuông 2x2. Trong hai ô còn lại, đúng một ô có block làm điểm tựa. """ dx = abs(c[0] - e[0]) dy = abs(c[1] - e[1]) if dx != 1 or dy != 1: return False n1 = (c[0], e[1]) n2 = (e[0], c[1]) in_n1 = n1 in blocks_set in_n2 = n2 in blocks_set return in_n1 ^ in_n2 def get_move_type_2d(c, e, blocks_set): if is_slide_2d(c, e, blocks_set): return "slide" if is_convex_2d(c, e, blocks_set): return "convex" return None def bounding_search_area_2d(blocks_set): min_x = min(x for x, y in blocks_set) - 1 max_x = max(x for x, y in blocks_set) + 1 min_y = min(y for x, y in blocks_set) - 1 max_y = max(y for x, y in blocks_set) + 1 return min_x, max_x, min_y, max_y def generate_valid_moves_2d(blocks_set): """ Sinh tất cả move hợp lệ trong vùng quanh cấu hình hiện tại. Điều kiện: - ô đích trống - không dùng tọa độ âm - là slide hoặc convex - C \ {c} liên thông - cấu hình sau move liên thông """ moves = [] if not blocks_set: return moves min_x, max_x, min_y, max_y = bounding_search_area_2d(blocks_set) empty_cells = [ (x, y) for x in range(min_x, max_x + 1) for y in range(min_y, max_y + 1) if (x, y) not in blocks_set and x >= 0 and y >= 0 ] for c in blocks_set: remaining = set(blocks_set) remaining.remove(c) if not is_connected(remaining, 2): continue for e in empty_cells: move_type = get_move_type_2d(c, e, blocks_set) if move_type is None: continue if is_move_connected(blocks_set, c, e, 2): moves.append((c, e, move_type)) return moves # ============================================================ # 2. Công cụ chọn move # ============================================================ def choose_best_reducing_move(blocks_set, candidate_moves): """ Chọn move làm giảm potential nhiều nhất. """ curr_pot = potential(blocks_set, 2) best = None best_pot = curr_pot for c, e, move_type, operation in candidate_moves: next_blocks = apply_move(blocks_set, c, e) next_pot = potential(next_blocks, 2) if next_pot < best_pot: best_pot = next_pot best = (c, e, move_type, operation, next_blocks, next_pot) return best def all_reducing_moves_with_operation(blocks_set, operation_name, filter_func=None): moves = [] for c, e, move_type in generate_valid_moves_2d(blocks_set): if filter_func is not None and not filter_func(c, e, move_type): continue next_blocks = apply_move(blocks_set, c, e) if potential(next_blocks, 2) < potential(blocks_set, 2): moves.append((c, e, move_type, operation_name)) return moves # ============================================================ # 3. Các bước paper-inspired cho bài toán 2D # ============================================================ def local_y_reduction(blocks_set): """ Bước 1: Local y-reduction. Phiên bản 2D của local z-reduction. Ưu tiên move làm giảm y, tức kéo ô xuống thấp hơn. """ def filt(c, e, move_type): return e[1] < c[1] candidates = all_reducing_moves_with_operation( blocks_set, "local_y_reduction", filt ) return choose_best_reducing_move(blocks_set, candidates) def get_vertical_columns(blocks_set): """ Nhóm các ô theo cùng x. Đây là phiên bản 2D của pillar theo trục y. """ columns = {} for x, y in blocks_set: columns.setdefault(x, []).append(y) result = {} for x, ys in columns.items(): ys = sorted(ys) segments = [] start = ys[0] prev = ys[0] for y in ys[1:]: if y == prev + 1: prev = y else: segments.append((start, prev)) start = y prev = y segments.append((start, prev)) result[x] = segments return result def is_in_nontrivial_column(c, blocks_set): """ Kiểm tra c có thuộc một cột dọc có độ dài >= 2 không. """ x, y = c count = 0 for bx, by in blocks_set: if bx == x: count += 1 return count >= 2 def column_shove(blocks_set): """ Bước 2: Column shove. Đây là bản đơn giản hóa của pillar shove trong 3D. Ta ưu tiên move thuộc một cột dọc, làm giảm potential. """ def filt(c, e, move_type): if not is_in_nontrivial_column(c, blocks_set): return False # Ưu tiên dịch xuống hoặc sang trái. return e[1] <= c[1] or e[0] < c[0] candidates = all_reducing_moves_with_operation( blocks_set, "column_shove", filt ) return choose_best_reducing_move(blocks_set, candidates) def local_potential_reduction(blocks_set): """ Bước 3: Local potential reduction. Nếu có bất kỳ move hợp lệ nào làm giảm potential thì chọn move tốt nhất. """ candidates = all_reducing_moves_with_operation( blocks_set, "local_potential_reduction" ) return choose_best_reducing_move(blocks_set, candidates) # ============================================================ # 4. Low / high components trong 2D # ============================================================ def connected_components_2d(cells): cells = set(cells) if not cells: return [] components = [] unvisited = set(cells) while unvisited: start = next(iter(unvisited)) comp = {start} queue = deque([start]) unvisited.remove(start) while queue: x, y = queue.popleft() for nb in [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]: if nb in unvisited: unvisited.remove(nb) comp.add(nb) queue.append(nb) components.append(comp) return components def get_low_high_components_2d(blocks_set): """ Trong 2D: - low cells: y = 0 - high cells: y > 0 """ low_cells = {b for b in blocks_set if b[1] == 0} high_cells = {b for b in blocks_set if b[1] > 0} low_components = connected_components_2d(low_cells) high_components = connected_components_2d(high_cells) return low_components, high_components def get_root_low_component(low_components): """ Root là low component chứa (0,0), nếu có. Nếu chưa có (0,0), chọn component đầu tiên. """ if not low_components: return None for comp in low_components: if (0, 0) in comp: return comp return low_components[0] def non_root_low_components(blocks_set): low_components, _ = get_low_high_components_2d(blocks_set) root = get_root_low_component(low_components) if root is None: return [] return [comp for comp in low_components if comp != root] def handling_low_components(blocks_set): """ Bước 4-5: Handling low components. Nếu có low component không phải root, thử move các ô trong đó hoặc gần đó để giảm potential và tiến về phía root. """ non_root = non_root_low_components(blocks_set) if not non_root: return None non_root_cells = set() for comp in non_root: non_root_cells.update(comp) def filt(c, e, move_type): # Ưu tiên xử lý các ô đáy không thuộc root if c not in non_root_cells: return False # Kéo về trái hoặc giữ y=0 return e[0] < c[0] or e[1] == 0 candidates = all_reducing_moves_with_operation( blocks_set, "handling_low_components", filt ) return choose_best_reducing_move(blocks_set, candidates) def is_small_low_component(component): """ Heuristic 2D: Một low component nhỏ nếu số ô của nó nhỏ hơn khoảng cách từ ô trái nhất của nó về gốc. """ if not component: return False min_x = min(x for x, y in component) return len(component) < min_x + 1 def small_low_components(blocks_set): """ Bước 6: Small low components. Với low component nhỏ, dùng nó như phần hỗ trợ. Trong code mô phỏng, ta thử move từ component nhỏ nếu làm giảm potential. """ comps = non_root_low_components(blocks_set) small_cells = set() for comp in comps: if is_small_low_component(comp): small_cells.update(comp) if not small_cells: return None def filt(c, e, move_type): return c in small_cells and (e[0] <= c[0] or e[1] <= c[1]) candidates = all_reducing_moves_with_operation( blocks_set, "small_low_components", filt ) return choose_best_reducing_move(blocks_set, candidates) def big_low_components(blocks_set): """ Bước 7: Big low components. Với low component lớn, ưu tiên kéo nó về phía gốc để nhập vào root. """ comps = non_root_low_components(blocks_set) big_cells = set() for comp in comps: if not is_small_low_component(comp): big_cells.update(comp) if not big_cells: return None def filt(c, e, move_type): return c in big_cells and e[0] < c[0] candidates = all_reducing_moves_with_operation( blocks_set, "big_low_components", filt ) return choose_best_reducing_move(blocks_set, candidates) # ============================================================ # 5. Vòng lặp chính # ============================================================ def choose_paper_inspired_move_2d(blocks_set): """ Thử lần lượt các bước theo thứ tự bạn rút ra từ bài báo. """ operations = [ local_y_reduction, column_shove, local_potential_reduction, handling_low_components, small_low_components, big_low_components ] for op in operations: move = op(blocks_set) if move is not None: return move return None def compact_2d_1(blocks, max_steps): """ Thuật toán 2D paper-inspired. Đây là bản mô phỏng có thứ tự các bước: local_y_reduction -> column_shove -> local_potential_reduction -> handling_low_components -> small_low_components -> big_low_components. """ blocks_set = to_tuple_set(blocks) curr_blocks = blocks_set curr_pot = potential(curr_blocks, 2) initial_info = { "blocks": to_list(curr_blocks), "potential": curr_pot, "is_connected": True, "is_finished": is_finished_2d(curr_blocks) } steps = [] status = "completed" if is_finished_2d(curr_blocks) else "running" if status == "completed": final_info = { "blocks": to_list(curr_blocks), "potential": curr_pot, "is_connected": True, "is_finished": True } return build_response( True, "Compaction completed successfully.", 2, initial_info, steps, final_info, status, algorithm="paper_inspired_2d" ) for step_idx in range(1, max_steps + 1): if is_finished_2d(curr_blocks): status = "completed" break chosen = choose_paper_inspired_move_2d(curr_blocks) if chosen is None: status = "no_valid_move_found" break c, e, move_type, operation, next_blocks, next_pot = chosen finished_after = is_finished_2d(next_blocks) connected_after = is_connected(next_blocks, 2) step_info = make_step_info( step_idx=step_idx, from_c=c, to_c=e, move_type=move_type, blocks_before=curr_blocks, blocks_after=next_blocks, pot_before=curr_pot, pot_after=next_pot, connected_after=connected_after, finished_after=finished_after, operation=operation ) steps.append(step_info) curr_blocks = next_blocks curr_pot = next_pot if finished_after: status = "completed" break if status == "running": status = "max_steps_reached" final_finished = is_finished_2d(curr_blocks) if final_finished: status = "completed" final_info = { "blocks": to_list(curr_blocks), "potential": curr_pot, "is_connected": is_connected(curr_blocks, 2), "is_finished": final_finished } if status == "completed": message = "Compaction completed successfully." elif status == "max_steps_reached": message = "Maximum number of steps reached before finishing compaction." else: message = "No valid move found." return build_response( True, message, 2, initial_info, steps, final_info, status, algorithm="paper_inspired_2d" )