[Procedural Programming + Ada] Bài 27 – Console Tic-Tac-Toe App (kết thúc)

Và đây là code hoàn thiện của Redirect_User_Concern; giúp bổ sung logic điều hướng bước đi tiếp theo mà User phải tạm dừng ý định tạo Double_Threat để ngăn chặn Computer thắng sớm hơn. Bây giờ chúng ta đã có thể bỏ đi các dòng Put_Line in ra số lượng Count_Double_Threats và các tập giá

Và đây là code hoàn thiện của Redirect_User_Concern; giúp bổ sung logic điều hướng bước đi tiếp theo mà User phải tạm dừng ý định tạo Double_Threat để ngăn chặn Computer thắng sớm hơn. Bây giờ chúng ta đã có thể bỏ đi các dòng Put_Line in ra số lượng Count_Double_Threats và các tập giá trị tiên đoán.

with Ada.Text_IO; use Ada.Text_IO;

package body AI_Mockup is

   procedure Redirect_User_Concern
      ( Computer_Move : in out Digit
      ; App_State : in State )
   is -- local
      Foreseen_User_Moves : Digit_Array;
      --
      Foreseen_Computer_Moves : Digit_Array;
      Foreseen_App_State : State;
      Computer_After_Next_Move: Digit := 0;
   begin
      if Count_Double_Threats (App_State) > 1 then
         Foreseen_User_Moves := (others => 0);
         Get_Double_Threats (Foreseen_User_Moves, App_State);
         --
         Foreseen_Computer_Moves := (others => 0);
         Get_Direct_Chance_Inits (Foreseen_Computer_Moves, App_State);
         --
         for Index in Foreseen_Computer_Moves'Range loop
            if Foreseen_Computer_Moves (Index) /= 0 then
               Copy (Foreseen_App_State, App_State);
               Update_Computer_Set (Foreseen_App_State, Foreseen_Computer_Moves (Index));
               Get_Direct_Chance (Computer_After_Next_Move, Foreseen_App_State);
               --
               if not Found (Computer_After_Next_Move, Foreseen_User_Moves) then
                  Computer_Move := Foreseen_Computer_Moves (Index);
               end if;
            end if;
         end loop;
      end if;
   end Redirect_User_Concern;

end AI_Mockup;

Bạn có thể chạy thử các trường hợp trước đó để kiểm tra hoạt động của logic mới bổ sung cho AI_Mockup.Get (Computer_Move, App_State);. Trên máy tính mình đang sử dụng thì kết quả khá ổn và chỉ hơi thiếu tự nhiên một chút bởi vì chúng ta chưa chia các tập kết quả tiềm năng ra để thực hiện lựa chọn ngẫu nhiên các bước đi có thể thực hiện. Tuy nhiên điều đó sẽ yêu cầu việc tái cấu trúc lại code kha khá thời gian và bạn nên cân nhắc sau khi đã đảm bảo rằng kết quả chạy thử hoàn toàn ổn.

Source Code : GitHub.IO

Mặc dù vẫn còn những tính năng khác nữa mà mình chưa tìm hiểu hết về ngôn ngữ Ada và môi trường phát triển của GNAT, tuy nhiên đây đã là điểm phù hợp để kết thúc Sub-Series này với mục đích chính là tìm hiểu về Procedural Programming trong môi trường phát triển được thiết kế đặc trưng cho mô hình lập trình này.

Cụ thể là trước khi chúng ta chuyển qua Sub-Series Functional Programming đã dự định trước đó thì mình muốn gạch đầu dòng lại một vài đặc trưng trong khía cạnh tư duy Procedural đã nghiệm thu được nhờ Ada:

  1. Luôn phân biệt rất rõ các loại sub-program theo mục đích sử dụng. Các thủ tục procedure được sử dụng để tạo ra hiệu ứng biên side-effect có ảnh hưởng tới các dữ liệu trạng thái của môi trường bên ngoài. Các hàm function được sử dụng để hỗ trợ các thao tác tính toán cục bộ và sẽ không bao giờ tạo ra hiệu ứng biên side-effect như các procedure.
  2. Các thủ tục procedure được sử dụng chủ đạo và các hàm function chỉ mang tính tiện ích bổ trợ. Các procedure sẽ đóng vai trò điều khiển logic hoạt động chính của chương trình. Các function tiện ích được dùng để giảm thiểu số lượng biến cục bộ local cho các procedure và ở những vị trí yêu cầu cú pháp sử dụng ở dạng biểu thức trả về giá trị, ví dụ như các contract.
  3. Trọng tâm của một chương trình là các dữ liệu trạng thái State sẽ được tạo ra và lưu cố định ở vị trí nào đó mang tính chất toàn cục global hoặc nếu lưu cục bộ thì sẽ là biến local của các procedure quan trọng góp phần điều khiển logic xử lý chính của chương trình.
  4. Địa chỉ tham chiếu tới các bộ dữ liệu trạng thái State sẽ được chia sẻ giữa các sub-program, và các procedure sẽ đóng vai trò là các hành động khách quan xung quanh để lần lượt tạo ra các sự thay đổi trên State. Để dễ hình dung hơn thì các State sẽ đóng vai trò là các Cơ Sở Dữ Liệu Mini trong thời gian chương trình vận hành và sẽ được cập nhật thay đổi nội dung nhiều lần.
  5. Các yếu tố ràng buộc contract sẽ giúp ích rất nhiều cho các sub-program; Ví dụ như các chế độ hoạt động của các tham số in, out, in out sẽ giúp thông báo lỗi nếu như chúng ta viết code triển khai logic không phù hợp; Và các kiểu contract tự định nghĩa khác có thể được gắn liền với các sub-program để đảm bảo tính hợp lệ của dữ liệu input/output.

Nói riêng về Ada thì sự minh bạch trong cú pháp của ngôn ngữ và phong cách đặt tên các yếu tố trong các thư viện tiêu chuẩn cũng giúp mình cải thiện được thói quen khi code. Tuy nhiên, riêng tính năng nhóm các tham số cùng kiểu thành các danh sách bằng các dấu chấm phẩy ; trong code khai báo các sub-program thì có phần hơi thừa. Mình thực sự đã phải chỉnh sửa lại code rất nhiều lần chỉ vì thói quen từ các ngôn ngữ khác là chỉ có một danh sách tham số duy nhất với các tham số được liệt kê với dấu phẩy ,.

function Area_of_Circle
   ( R : Float
   ; X , Y : Integer )
return Float;

Tuy nhiên đó chỉ là ý kiến cá nhân của một beginner đang chập chững học code. Bởi có lẽ đối với các kỹ sư lập trình điều khiển cho các engine phần cứng như những chiếc máy máy bay thương mại thì có lẽ đây lại là một tính năng hữu ích khi thiết kế các sub-program có nhiều nhóm tham số khác nhau.

Điểm duy nhất mà mình có thể mạnh dạn khẳng định đó là khả năng định dạng kết quả in ra cửa sổ Console của Ada là rất giới hạn bởi thiết kế định kiểu chặt chẽ và không có tùy chọn để người viết code định nghĩa lại các thuộc tính Type'Attribute. Khi sử dụng Integer'Image (Value) với các giá trị số nguyên có một chữ số đơn thì kết quả thu được sẽ luôn là một chuỗi kèm theo một ký tự khoảng trống ở phía trước. Và chúng ta cũng không có tính năng sử dụng tham số định dạng trong C ví dụ như %9s để in ra chuỗi kết quả dài 9 ký tự mặc dù dữ liệu truyền vào printf có thể ngắn hơn nhiều.

Nếu có dự định tạo ra một ứng dụng khác sau project này thì chắc chắn mình sẽ chỉ sử dụng Ada để code xử lý logic chính. Còn code để vẽ giao diện người dùng thì chắc chắn là sẽ viết bằng Cimport vào Ada để sử dụng sau. Và sự kết hợp như vậy có lẽ rất hoàn hảo cho cả AdaC; bởi Ada thì không hẳn phổ biến trong lập trình ứng dụng phổ thông General Purposes nên sẽ cần phải giao tiếp với các API của các môi trường khác bằng C; còn C thì lại không có sẵn các cấu trúc dữ liệu phổ biến như Map, Set, Tree, v.v… và nhiều tính năng hỗ trợ ở cấp độ ngôn ngữ và giao diện lập trình bậc cao như contract, và cả generic trong C cũng rất khó triển khai.

Bây giờ thì chúng ta sẽ chuyển sang mô hình lập trình tiếp theo trong nhóm các mô hình lập trình phổ biến. Hy vọng là bạn vẫn còn nhớ các cú pháp của Elm và nền móng tư duy căn bản của Declarative. Nếu bạn đã quên thì hãy dành một chút thời gian đọc lướt qua các bài viết trước đó của Sub-Series [Declarative Programming + Elm]; Bởi vì ngôn ngữ mà chúng ta sẽ tìm hiểu và làm ví dụ cho Sub-Series tiếp theo có thể được xem là phiên bản đầy đủ hơn của Elm về các tính năng được hỗ trợ ở cấp độ cú pháp của ngôn ngữ.

(chưa đăng tải) [Functional Programming + Haskell] Bài 1 – Giới Thiệu Ngôn Ngữ Haskell

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ