File size: 7,504 Bytes
987f097
f512021
 
 
 
 
 
 
 
 
 
 
c728f53
 
f512021
 
 
 
 
c728f53
 
 
 
 
 
 
987f097
f512021
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
987f097
f512021
 
 
 
 
 
 
 
 
 
 
 
987f097
 
f512021
987f097
 
f512021
c728f53
 
f512021
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
987f097
f512021
 
c728f53
 
f512021
c728f53
 
f512021
987f097
f512021
 
 
 
987f097
f512021
 
987f097
 
f512021
987f097
 
f512021
987f097
 
 
f512021
c728f53
 
 
 
 
 
f512021
c728f53
 
 
 
f512021
c728f53
 
 
 
 
 
 
f512021
 
 
 
 
 
 
 
 
 
 
987f097
 
f512021
c728f53
 
f512021
987f097
 
 
c728f53
987f097
 
 
f512021
987f097
c728f53
987f097
f512021
987f097
c728f53
 
 
f512021
 
 
 
 
 
 
 
 
 
c728f53
 
 
f512021
c728f53
 
 
 
 
 
f512021
c728f53
 
 
 
 
 
 
f512021
c728f53
 
 
 
 
 
 
 
 
 
 
f512021
 
 
 
c728f53
 
f512021
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# 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
    )