Giới thiệu
Trong thế giới lập trình, có rất nhiều thử thách thú vị mà bạn có thể thực hiện để kiểm tra khả năng của mình. Một trong những thử thách nổi tiếng là tạo ra một hình khối 3D quay trong một cửa sổ console. Trong bài viết này, chúng ta sẽ sử dụng ngôn ngữ lập trình jdBasic để thực hiện điều này. Hãy cùng khám phá cách chúng ta có thể tạo ra hình khối 3D hoàn chỉnh chỉ với các ký tự ASCII và một ít toán học.
Công cụ: jdBasic là gì?
jdBasic là một ngôn ngữ lập trình mà tôi đã phát triển để kết hợp sự đơn giản của BASIC cổ điển với sức mạnh của các mô hình lập trình hiện đại. Ngôn ngữ này được viết bằng C++20 và cung cấp nhiều tính năng hữu ích:
- Lập trình mảng theo phong cách APL: Bạn có thể thực hiện các phép toán trên toàn bộ ma trận chỉ trong một dòng mà không cần vòng lặp.
- Động cơ AI tích hợp: Nó hỗ trợ các đối tượng
Tensorvà tự động phân biệt để xây dựng và đào tạo mạng nơ-ron. - Các tính năng hiện đại: Hỗ trợ
Maps(từ điển), xử lý lỗi vớiTRY...CATCH, và có thể mở rộng bằng C++ DLLs.
Bạn có thể tìm hiểu thêm về mã nguồn của trình thông dịch và xem thêm ví dụ tại kho GitHub chính thức: jdBasic Repository.
Giải mã hình khối
Để tạo ra hoạt ảnh hình khối 3D quay này, chúng ta cần thực hiện bốn bước chính trong mỗi khung hình:
- Định nghĩa hình học: Chúng ta bắt đầu với hai ma trận: một cho 8 đỉnh (X, Y, Z) của hình khối và một cho 6 mặt bằng cách tham chiếu đến chỉ số của các đỉnh.
- Áp dụng xoay: Sử dụng ma trận quay trigonometry tiêu chuẩn để tính toán vị trí mới của từng đỉnh khi hình khối quay.
- Chiếu 2D: Giả lập phép chiếu để làm cho đối tượng 3D trông đúng trên màn hình 2D. Quy tắc đơn giản là: các đối tượng xa hơn sẽ nhỏ hơn. Tất cả các phép tính này được thực hiện mà không cần bất kỳ vòng lặp
FORnào. - Sử dụng thuật toán của họa sĩ: Để làm cho hình khối trở nên rắn chắc và ẩn các mặt phía sau, chúng ta tính toán độ sâu trung bình của mỗi mặt, sắp xếp chúng từ mặt sau đến mặt trước và vẽ theo thứ tự đó.
Mã nguồn hoàn chỉnh
Dưới đây là mã nguồn đầy đủ. Sức mạnh của các hàm định hướng mảng trong jdBasic giúp mã này trở nên gọn gàng và dễ hiểu:
basic
' ==========================================================
' == Hình Khối 3D ASCII Tối Ưu Hóa Hoàn Chỉnh
' ==========================================================
' --- Phần Cấu Hình ---
SCREEN_WIDTH = 40
SCREEN_HEIGHT = 20
' --- 1. Định nghĩa Đỉnh, Mặt và Biến Đổi ---
DIM Vertices[8, 3]
Vertices = RESHAPE([-1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1, -1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1], [8, 3])
DIM Faces[6, 4]
Faces = RESHAPE([4,5,6,7, 0,3,2,1, 0,1,5,4, 2,3,7,6, 1,2,6,5, 3,0,4,7], [6, 4])
DIM FaceChars$[6]
FaceChars$ = ["#", "O", "+", "=", "*", "."]
DIM ScalingMatrix[3, 3]
ScalingMatrix = [[1,0,0], [0,2,0], [0,0,1]]
' --- 2. Vòng Lặp Hoạt Hình Chính ---
DIM ScreenBuffer[SCREEN_HEIGHT, SCREEN_WIDTH]
DIM ProjectedPoints[8, 2]
AngleX = 0 : AngleY = 0
FocalLength = 5
' --- 3. Hàm cho việc Xoay và Vẽ ---
FUNC CreateRotationX(angle)
RETURN [[1, 0, 0], [0, COS(angle), -SIN(angle)], [0, SIN(angle), COS(angle)]]
ENDFUNC
FUNC CreateRotationY(angle)
RETURN [[COS(angle), 0, SIN(angle)], [0, 1, 0], [-SIN(angle), 0, COS(angle)]]
ENDFUNC
SUB DrawLine(x1, y1, x2, y2, char$)
dx = ABS(x2 - x1) : dy = -ABS(y2 - y1)
sx = -1: IF x1 < x2 THEN sx = 1
sy = -1: IF y1 < y2 THEN sy = 1
err = dx + dy
DO
IF x1 >= 0 AND x1 < SCREEN_WIDTH AND y1 >= 0 AND y1 < SCREEN_HEIGHT THEN ScreenBuffer[y1, x1] = char$
IF x1 = x2 AND y1 = y2 THEN EXITDO
e2 = 2 * err
IF e2 >= dy THEN err = err + dy : x1 = x1 + sx
IF e2 <= dx THEN err = err + dx : y1 = y1 + sy
LOOP
ENDSUB
SUB FillFace(points, char$)
y_min = MAX([0, MIN(SLICE(points, 1, 1))])
y_max = MIN([SCREEN_HEIGHT - 1, MAX(SLICE(points, 1, 1))])
FOR y = y_min TO y_max
intersections = []
FOR i = 0 TO 3
p1 = SLICE(points, 0, i)
p2 = SLICE(points, 0, (i + 1) MOD 4)
IF p1[1] <> p2[1] AND ((y >= p1[1] AND y < p2[1]) OR (y >= p2[1] AND y < p1[1])) THEN
intersections = APPEND(intersections, p1[0] + (y - p1[1]) * (p2[0] - p1[0]) / (p2[1] - p1[1]))
ENDIF
NEXT i
IF LEN(intersections) >= 2 THEN
FOR x = MAX([0, MIN(intersections)]) TO MIN([SCREEN_WIDTH - 1, MAX(intersections)])
ScreenBuffer[y, x] = char$
NEXT x
ENDIF
NEXT y
ENDSUB
CURSOR FALSE
F = 0
DO
' --- a) Xoay & Tỉ lệ Đỉnh ---
CombinedTransform = MATMUL(ScalingMatrix, MATMUL(CreateRotationY(AngleY), CreateRotationX(AngleX)))
RotatedVertices = MATMUL(Vertices, CombinedTransform)
' --- b) Chiếu theo phương pháp Vector hóa ---
X_col = SLICE(RotatedVertices, 1, 0)
Y_col = SLICE(RotatedVertices, 1, 1)
Z_col = SLICE(RotatedVertices, 1, 2)
Perspective = FocalLength / (FocalLength - Z_col)
ScreenX = (X_col * Perspective) * (SCREEN_WIDTH / 8) + (SCREEN_WIDTH / 2)
ScreenY = (Y_col * Perspective) * (SCREEN_HEIGHT / 8) + (SCREEN_HEIGHT / 2)
ProjectedPoints = STACK(1, INT(ScreenX), INT(ScreenY))
' --- c) Thuật toán Painter theo phương pháp Vector hóa ---
FaceZCoords = RESHAPE(Z_col[Faces], [6, 4])
FaceDepths = SUM(FaceZCoords, 1) / 4
DrawOrder = GRADE(FaceDepths)
' --- d) Vẽ Mặt từ Sau ra Trước ---
ScreenBuffer = RESHAPE([" "], [SCREEN_HEIGHT, SCREEN_WIDTH])
FOR i = 0 TO 5
face_idx = DrawOrder[i,0]
v_indices = SLICE(Faces, 0, face_idx)
face_x_coords = SLICE(ProjectedPoints, 1, 0)[v_indices]
face_y_coords = SLICE(ProjectedPoints, 1, 1)[v_indices]
face_points = STACK(1, face_x_coords, face_y_coords)
FillFace face_points, FaceChars$[face_idx]
FOR p = 0 TO 3
p1 = SLICE(face_points, 0, p)
p2 = SLICE(face_points, 0, (p + 1) MOD 4)
DrawLine p1[0], p1[1], p2[0], p2[1], "*"
NEXT p
NEXT i
' --- e) Hiển thị và Cập nhật ---
CLS
PRINT "Hình Khối 3D Tối Ưu (Nhấn phím bất kỳ)"
PRINT FRMV$(ScreenBuffer)
YIELD
AngleX = AngleX + 0.05 : AngleY = AngleY + 0.08
F = F + 1
LOOP UNTIL F > 200
CURSOR TRUE
Thử Nghiệm Ngay
Bạn có thể chạy mã này ngay bây giờ trong trình duyệt của bạn mà không cần cài đặt:
- Truy cập jdBasic Web REPL: jdBasic REPL
- Gõ
EDITvà nhấn Enter. - Dán toàn bộ mã ở trên vào trình soạn thảo.
- Nhấp vào nút "Save & Close".
- Gõ
RUNvà nhấn Enter.
Hãy thưởng thức chương trình! Chúc bạn lập trình vui vẻ!