sliding-cubes-api / algorithm_3d.py
quynhanh2610's picture
Update algorithm_3d.py
f512021 verified
Raw
History Blame Contribute Delete
7.5 kB
# algorithm_3d.py
from utils import (
to_tuple_set,
to_list,
is_move_connected,
is_connected,
potential,
apply_move,
make_step_info,
build_response
)
def is_finished_3d(blocks_set):
"""
Kiểm tra cấu hình 3D đã nén xong chưa.
Theo định nghĩa: với mỗi cube (x,y,z), toàn bộ cuboid
từ (0,0,0) đến (x,y,z) phải có mặt trong cấu hình.
"""
for x, y, z in blocks_set:
for i in range(x + 1):
for j in range(y + 1):
for k in range(z + 1):
if (i, j, k) not in blocks_set:
return False
return True
def add_vec(a, b):
return tuple(a[i] + b[i] for i in range(3))
def sub_vec(a, b):
return tuple(a[i] - b[i] for i in range(3))
def is_unit_axis_vector(v):
"""
Kiểm tra vector có phải dạng:
(±1,0,0), (0,±1,0), (0,0,±1)
"""
return sum(abs(x) for x in v) == 1
def perpendicular_axis_vectors(direction):
"""
Với hướng slide direction, trả về 4 vector vuông góc theo 2 trục còn lại.
Ví dụ:
direction = (1,0,0)
thì các hướng vuông góc là:
(0,1,0), (0,-1,0), (0,0,1), (0,0,-1)
"""
result = []
for axis in range(3):
if direction[axis] == 0:
v_pos = [0, 0, 0]
v_neg = [0, 0, 0]
v_pos[axis] = 1
v_neg[axis] = -1
result.append(tuple(v_pos))
result.append(tuple(v_neg))
return result
def is_slide_3d(c, e, blocks_set):
"""
Slide trong 3D:
- c và e phải kề mặt nhau.
- Tồn tại một mặt phẳng 2D chứa c và e sao cho trong 4-cycle:
c, e, c + p, e + p
thì e là ô trống, còn c, c+p, e+p đều có cube.
"""
direction = sub_vec(e, c)
if not is_unit_axis_vector(direction):
return False
for p in perpendicular_axis_vectors(direction):
c_side = add_vec(c, p)
e_side = add_vec(e, p)
if c_side in blocks_set and e_side in blocks_set:
return True
return False
def is_convex_3d(c, e, blocks_set):
"""
Convex transition trong 3D:
- c và e nằm chéo nhau trong một mặt phẳng tọa độ.
- Nghĩa là chúng khác nhau đúng 2 trục, mỗi trục lệch 1 đơn vị.
- Trong 4-cycle đó, đúng một trong hai ô trung gian có cube.
"""
diff = sub_vec(e, c)
changed_axes = [i for i in range(3) if diff[i] != 0]
# Convex phải lệch đúng 2 trục
if len(changed_axes) != 2:
return False
# Mỗi trục lệch đúng 1
if any(abs(diff[i]) != 1 for i in changed_axes):
return False
# Trục còn lại phải giữ nguyên
if sum(abs(x) for x in diff) != 2:
return False
a, b = changed_axes
n1 = list(c)
n1[a] = e[a]
n1 = tuple(n1)
n2 = list(c)
n2[b] = e[b]
n2 = tuple(n2)
in_n1 = n1 in blocks_set
in_n2 = n2 in blocks_set
return in_n1 ^ in_n2
def is_valid_move_3d(c, e, blocks_set):
"""
Trả về loại move nếu hợp lệ về mặt hình học:
- "slide"
- "convex"
- None nếu không hợp lệ
"""
if is_slide_3d(c, e, blocks_set):
return "slide"
if is_convex_3d(c, e, blocks_set):
return "convex"
return None
def generate_valid_moves_3d(blocks_set):
moves = []
if not blocks_set:
return moves
min_x = min(b[0] for b in blocks_set) - 1
max_x = max(b[0] for b in blocks_set) + 1
min_y = min(b[1] for b in blocks_set) - 1
max_y = max(b[1] for b in blocks_set) + 1
min_z = min(b[2] for b in blocks_set) - 1
max_z = max(b[2] for b in blocks_set) + 1
empty_cells = [
(x, y, z)
for x in range(min_x, max_x + 1)
for y in range(min_y, max_y + 1)
for z in range(min_z, max_z + 1)
if (x, y, z) not in blocks_set
]
for c in blocks_set:
# Điều kiện quan trọng của bài báo:
# Trong lúc move, C \ {c} phải liên thông.
remaining = set(blocks_set)
remaining.remove(c)
if not is_connected(remaining, 3):
continue
for e in empty_cells:
# Bản simulator hiện tại chưa hỗ trợ tọa độ âm
if e[0] < 0 or e[1] < 0 or e[2] < 0:
continue
move_type = is_valid_move_3d(c, e, blocks_set)
# Kiểm tra thêm trạng thái sau khi đặt cube xuống vẫn liên thông
if move_type and is_move_connected(blocks_set, c, e, 3):
moves.append((c, e, move_type))
return moves
def compact_3d(blocks, max_steps):
blocks_set = to_tuple_set(blocks)
pot_initial = potential(blocks_set, 3)
initial_info = {
"blocks": blocks,
"potential": pot_initial,
"is_connected": True,
"is_finished": is_finished_3d(blocks_set)
}
steps = []
curr_blocks = blocks_set
curr_pot = pot_initial
status = "completed" if is_finished_3d(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.",
3,
initial_info,
steps,
final_info,
status
)
for step_idx in range(1, max_steps + 1):
valid_moves = generate_valid_moves_3d(curr_blocks)
best_move = None
best_pot = curr_pot
for c, e, move_type in valid_moves:
next_blocks = apply_move(curr_blocks, c, e)
next_pot = potential(next_blocks, 3)
if next_pot < best_pot:
best_pot = next_pot
best_move = (c, e, move_type, next_blocks)
if not best_move:
status = "completed" if is_finished_3d(curr_blocks) else "no_valid_move_found"
break
c, e, move_type, next_blocks = best_move
finished_after = is_finished_3d(next_blocks)
step_info = make_step_info(
step_idx,
c,
e,
move_type,
curr_blocks,
next_blocks,
curr_pot,
best_pot,
True,
finished_after
)
steps.append(step_info)
curr_blocks = next_blocks
curr_pot = best_pot
if finished_after:
status = "completed"
break
if status == "running":
if len(steps) == max_steps:
status = "max_steps_reached"
else:
status = "no_valid_move_found"
final_finished = is_finished_3d(curr_blocks)
if final_finished:
status = "completed"
final_info = {
"blocks": to_list(curr_blocks),
"potential": curr_pot,
"is_connected": True,
"is_finished": final_finished
}
message = (
"Compaction completed successfully."
if status == "completed"
else "No valid move found."
if status == "no_valid_move_found"
else "Maximum number of steps reached before finishing compaction."
)
return build_response(
True,
message,
3,
initial_info,
steps,
final_info,
status
)