Ghi Chú Phát Triển (8) : Thiết Kế Ngôn Ngữ DSL Cho Công Thức Thiết Kế Game
Sáng nay tôi thức dậy cực sớm, đến văn phòng trước cả 11 giờ. Buổi trưa có hơn một tiếng rảnh rỗi để xử lý các công việc vụn vặt.
Tôi đồng bộ thông tin với Ốc Sên về những gì đã hoàn thành cuối tuần rồi, đồng thời nhận thêm vài yêu cầu mới. Chủ yếu là cần bổ sung khái niệm tương đương “service” trong protobuf vào định nghĩa giao thức. Sau khi cân nhắc kỹ lưỡng, tôi quyết định dành 1 tiếng đồng hồ hiện thực hóa tính năng này. Về phần C Binding API vẫn còn một số chỗ chưa hoàn thiện, chủ yếu do gặp khó khăn nhỏ khi tích hợp với framework Erlang. Tôi tinh chỉnh nhẹ giao diện C và mọi thứ liền hoạt động ổn thỏa. Sau đó vui vẻ đi ăn trưa.
Sau bữa trưa, tôi chuyển hướng tập trung vào phân tích yêu cầu từ phía Client.
Trong quá trình sinh dữ liệu animation tree, chúng tôi sử dụng một định dạng trung gian dạng text tiếng Việt, sau đó chuyển đổi thành định dạng nhị phân thông qua protobuf. Để xử lý định dạng text này, tôi quyết định dùng lại LPEG - công cụ tôi đã từng sử dụng nhiều năm trước (tôi từng viết bộ parser protobuf text bằng LPEG). Đây đúng là công cụ lợi hại để xử lý text.
Trong lúc ăn trưa, cả nhóm ngồi bàn tán về Java. Tất cả cùng đồng cảm với việc cộng đồng Java quá phụ thuộc vào XML như vật cứu mạng, dẫn đến hàng loạt framework XML phức tạp, đau đầu. Nếu những người sáng lập Java sớm chịu ảnh hưởng bởi tư duy Unix/C, họ sẽ hiểu rõ lợi ích của việc thiết kế DSL để giải quyết vấn đề. Thay vì chết chìm trong rừng XML như hiện tại. Thật đáng tiếc khi hiện trạng đã quá nặng nề đến mức khó sửa chữa.
Chiều nay chính thức gặp mặt các nhân viên thiết kế game để xem xét các bảng Excel họ xây dựng gần đây. Tôi nói với họ: “Cứ thoải mái diễn đạt ý tưởng của các bạn đi! Miễn là logic rõ ràng, thông tin đầy đủ, các bạn muốn trình bày công thức thế nào cũng được. Tôi sẽ từ từ nghiên cứu, chuẩn hóa cách viết để tiện cho lập trình xử lý sau này.”
Tôi từng chứng kiến nhiều dự án áp dụng các định dạng Excel phức tạp rồi export sang chương trình, hoặc tệ hơn là bắt các nhà thiết kế viết code trực tiếp, thậm chí làm những editor UI hoành tráng để thiết kế công thức. Theo tôi, cách hiệu quả nhất là thiết kế một ngôn ngữ DSL tối giản, vừa đủ đáp ứng nhu cầu, đồng thời được các nhà thiết kế chấp nhận.
Tại sao lại chọn DSL thay vì code bằng ngôn ngữ lập trình cụ thể? Vì ngôn ngữ phổ thông thường chứa nhiều cú pháp thừa, gây xao lãng tư duy của các nhà thiết kế. Họ sẽ mất thời gian học cú pháp, sửa lỗi thay vì tập trung vào logic game.
Hơn nữa, việc phụ thuộc vào một ngôn ngữ cụ thể sẽ giới hạn khả năng phát triển của dự án. Có thể sau này bạn muốn thay đổi cách xử lý công thức - khi không quan trọng hiệu năng thì dùng Lua, khi cần hiệu năng cao thì chuyển sang C… Một ngôn ngữ DSL trung lập sẽ mở ra nhiều lựa chọn tối ưu hóa hơn.
Chúng tôi cố gắng tối giản hóa các kiến thức phụ thuộc trong module xử lý công thức, chỉ giữ lại một DSL đơn giản.
Tất nhiên, việc định nghĩa DSL cũng cần nhiều kinh nghiệm thiết kế ngôn ngữ và hiểu biết sâu sắc về lĩnh vực bài toán. Hai điều này tôi đều chưa đủ. Nhưng cứ thử nghiệm trước đã. Với quy mô nhóm nhỏ như hiện tại, chúng tôi hoàn toàn có thể chấp nhận những thay đổi nhất định. Đặc biệt vì DSL là do tôi tự xây dựng, khi có thay đổi lớn, tôi có thể tự viết tool để cập nhật hàng loạt các file cũ.
Trong vài giờ đồng hồ, tôi đã hiểu rõ yêu cầu và phác thảo bản nháp DSL.
Các nhà thiết kế cần một ngôn ngữ cho phép viết các công thức toán học cơ bản (cộng trừ nhân chia), dựa trên các biến (thường là thuộc tính nhân vật) để tính toán giá trị mới và gán cho biến khác. Khi công thức phức tạp, họ muốn tự định nghĩa các hàm - hầu hết là hàm n:1 (nhập n tham số, xuất một giá trị).
Hai hàm ngoại lai phổ biến nhất (các nhà thiết kế không suy nghĩ như lập trình viên - họ không coi thao tác ngoài toán học là “hàm”, nhưng lập trình viên thì khác, và tôi có thể khéo léo truyền đạt khái niệm này): thứ nhất là tra cứu bảng số (làm một bảng Excel 2 chiều, tìm giá trị tại tọa độ hàng/cột cụ thể). Thực ra đây là vector 3 chiều: tên bảng là chiều thứ nhất, hàng/cột là hai chiều còn lại. Thứ hai là tạo số ngẫu nhiên.
Với hai chức năng này, gần như có thể đáp ứng mọi nhu cầu tính toán của các nhà thiết kế.
Tuy nhiên một số công thức còn cần thêm điều khiển luồng. Lấy ví dụ yêu thích từ World of Warcraft của một bạn thiết kế: cần sinh số ngẫu nhiên để kiểm tra đòn đánh có bị né tránh không; nếu né, các bước tính toán tiếp theo không cần thực hiện nữa. Nếu không né thì tiếp tục xử lý. Về lý thuyết có thể thiết kế điều khiển luồng trong DSL, nhưng tôi thấy chưa cần thiết. Theo tôi, nếu khả năng thực thi tuần tự từng biểu thức đã đủ đáp ứng, thì không nên thêm kiến thức mới. Tôi quyết định giới thiệu quy tắc logic boolean cho các nhà thiết kế.
Vì mọi người đều tốt nghiệp chuyên ngành kỹ thuật nên rất nhanh hiểu. Quy tắc short-circuit and/or/not kiểu Lua rất đơn giản, chỉ cần vài ví dụ là họ nắm bắt ngay. Cuối cùng tôi đã tạo ra thứ gì đó khá thô sơ nhưng hiệu quả:
|
|
Có một bảng “cri” được mô tả dưới dạng text như sau (sẽ bổ sung hỗ trợ định dạng Excel trong vòng nửa tiếng tới):
|
|
Hôm nay tôi không viết giải thích chi tiết trên blog làm gì, chắc chắn sau này sẽ phải dành thời gian viết tài