[Procedural Programming + Ada] Bài 22 – Console Tic-Tac-Toe App (tiếp theo)

Trong số các thủ tục được gọi ở lượt di chuyển của Computer thì chúng ta chỉ còn Get_Computer_Move; và Update_Computer_Set; là các thủ tục mới. Tuy nhiên Update_Computer_Set; có logic tương đồng với Update_User_Set; vì vậy nên ở đây mình sẽ chỉ đặt code ví dụ và chúng ta sẽ chuyển qua nói về

Trong số các thủ tục được gọi ở lượt di chuyển của Computer thì chúng ta chỉ còn Get_Computer_Move;Update_Computer_Set; là các thủ tục mới. Tuy nhiên Update_Computer_Set; có logic tương đồng với Update_User_Set; vì vậy nên ở đây mình sẽ chỉ đặt code ví dụ và chúng ta sẽ chuyển qua nói về Get_Computer_Move; luôn.

Update_Computer_Set;

Khai báo Update_Computer_Set gắn kèm các contract để đảm bảo các *_Set sau thao tác cập nhật vẫn chứa nội dung bổ trợ lẫn nhau và không có ô cờ nào bị lặp.

package App_Model is

   -- ...

   procedure Update_User_Set (App_State : out State; User_Move : in Digit)
   with Post => Areas_Are_Unique (App_State) and Sets_Are_Complement (App_State);

   procedure Update_Computer_Set (App_State : out State; Computer_Move : in Digit)
   with Post => Areas_Are_Unique (App_State) and Sets_Are_Complement (App_State);
   
   -- ...
   
end App_Model;

Xóa tại Common_Set và thêm mới tại Computer_Set.

package body App_Model is

   -- ...
   
   procedure Update_Computer_Set
      ( App_State : out State
      ; Computer_Move : in Digit )
   is -- local
      Index : Integer := Computer_Move;
   begin
      App_State.Common_Set (Index) := 0;
      App_State.Computer_Set (Index) := Computer_Move;
   end Update_Computer_Set;
   
   -- ...
   
end App_Model;

Get_Computer_Move;

Ở đây chúng ta cần xác định mục tiêu xây dựng cho code xử lý logic tính toán và tạo bước đi cho Computer. Hiển nhiên nếu chúng ta muốn tạo ra một logic tính toán linh động và tự nhiên gần giống với phương thức suy nghĩ của con người thì code triển khai sẽ cần thêm nhiều thao tác bổ trợ. Còn nếu chúng ta chỉ đặt ra mục tiêu là một logic tính toán để có được kết quả hòa và không cần thêm các yếu tố khác như chọn bước đi ngẫu nhiên thì code triển khai cũng không quá phức tạp.

Lựa chọn của mỗi người có lẽ sẽ khác nhau, tuy nhiên điểm chung vẫn là chúng ta sẽ xuất phát với các trường hợp tư duy logic đơn giản và phát triển dần để mở rộng các trường hợp cần xử lý trong code. Và để thuận tiện cho việc mở rộng logic tính toán hỗ trợ cho Get_Computer_Move; khi cần thiết thì chúng ta sẽ tạo thêm một package riêng cho procedure này và các sub-program hỗ trợ xoay quanh đó tên là AI_Mockup.

src
├── ai-mockup
│   ├── ai_mockup.adb
│   └── ai_mockup.ads
├── app-state
│   ├── app_state.adb
│   └── app_state.ads
├── console-io
│   ├── console_io.adb
│   └── console_io.ads
├── src.map
└── main.adb

Trường hợp đơn giản nhất là các bước đi ở thế cân bằng ở thời điểm mở đầu ván cờ, chúng ta thường sẽ ưu tiên chọn các ô cờ quan trọng và có nhiều đường thẳng đi qua nhất. Như vậy giải pháp đơn giản là chúng ta có thể tạo ra một mảng liệt kê đầy đủ các ô trên bàn cờ nhưng được sắp xếp theo mức độ quan trọng gảm dần. Sau đó lặp qua mảng này và lấy ra từng phần tử để so sánh và kiểm tra sự có mặt trong Common_Set để chọn bước di chuyển hợp lệ.

with App_Model; use App_Model;

package AI_Mockup is
   procedure Get (Computer_Move : out Digit; App_State : in State);
end AI_Mockup;
package body AI_Mockup is

   procedure Get
      ( Computer_Move : out Digit
      ; App_State : in State )
   is -- local
      Prioritized_Set : Digit_Array;
      Area : Digit;
   begin
      Prioritized_Set := (5, 2, 4, 6, 8, 1, 3, 7, 9);
      --
      for Index in Prioritized_Set'Range loop
         Area := Prioritized_Set (Index);
         if Found (Area, App_State.Common_Set) then
            Computer_Move := Area; exit;
         end if;
      end loop;
      --
      Put_Line ("Computer move:" & Digit'Image (Computer_Move));
   end Get;
   
end AI_Mockup;

Lúc này ở code sử dụng tại Main khi chúng ta nhúng thêm package AI_Mockup bằng lệnh use thì đã có sự trùng lặp về chữ ký signature của procedure Get (out Digit, in State);. Do đó nên chúng ta sẽ phải sửa lại lệnh gọi tên thủ tục Get ở lượt di chuyển của User thành dạng tham chiếu từ tên package để trình biên dịch có thể xác định được chính xác định nghĩa của mỗi procedure mà chúng ta sử dụng.

with Ada.Text_IO; use Ada.Text_IO;
with App_Model; use App_Model;
with Console_IO; use Console_IO;
with AI_Mockup; use AI_Mockup;

procedure Main is
   User_Symbol : Symbol;
   App_State : State;
   User_Move : Digit;
   Computer_Move : Digit;
begin
   Put_Symbol_Menu;
   Get (User_Symbol);

   Init (App_State);
   Put_Chess_Board (App_State, User_Symbol);

   -- loop
      Console_IO.Get (User_Move, App_State);
      Update_User_Set (App_State, User_Move);
      Update_Match_Status (App_State);
      Put_Line (Status'Image (App_State.Match_Status) & " ...");
      Put_Chess_Board (App_State, User_Symbol);
      --
      AI_Mockup.Get (Computer_Move, App_State);
      Update_Computer_Set (App_State, Computer_Move);
      Update_Match_Status (App_State);
      Put_Line (Status'Image (App_State.Match_Status) & " ...");
      Put_Chess_Board (App_State, User_Symbol);
      --
      -- exit when App_State.Match_Status /= PLAYING then exit; end if;
   -- end loop;
end Main;
alr run

Choose your Symbol ...
   1. Letter 'X'
   2. Letter 'O'
Your choice: 1
You've chosen: X

+ - - + - - + - - +
|  2  |  9  |  4  |
+ - - + - - + - - +
|  7  |  5  |  3  |
+ - - + - - + - - +
|  6  |  1  |  8  |
+ - - + - - + - - +
Your move: 1

PLAYING ...
+ - - + - - + - - +
|  2  |  9  |  4  |
+ - - + - - + - - +
|  7  |  5  |  3  |
+ - - + - - + - - +
|  6  |  X  |  8  |
+ - - + - - + - - +
Computer move: 5

PLAYING ...
+ - - + - - + - - +
|  2  |  9  |  4  |
+ - - + - - + - - +
|  7  |  O  |  3  |
+ - - + - - + - - +
|  6  |  X  |  8  |
+ - - + - - + - - +

Bây giờ thì chúng ta đã có thể bỏ comment cấu trúc vòng lặp để đánh cờ với Computer cho đến khi ván cờ kết thúc. Để duy trì logic đơn giản ở thời điểm khởi đầu thì mình đã đặt tạm một vòng lặp dạng do .. while. Tuy nhiên logic dừng vòng lặp mà chúng ta cần ở đây lại yêu cầu sự linh động nhiều hơn một chút. Chúng ta sẽ cần kiểm tra ngay sau mỗi bước di chuyển của User hoặc Computer để đưa quyết định tiếp tục hoặc dừng vòng lặp dựa trên trạng thái của ván cờ Match_Status.

procedure Main is
   -- local ...
begin
   Put_Symbol_Menu;
   Get (User_Symbol);

   Init (App_State);
   Put_Chess_Board  ...

   loop
      Console_IO.Get ... User_Move
      Update_User_Set ...
      Update_Match_Status ...
      Put_Line ... Match_Status
      Put_Chess_Board ...
      --
      if App_State.Match_Status /= PLAYING then exit; end if;
      --
      AI_Mockup.Get  ... Computer_Move
      Update_Computer_Set  ...
      Update_Match_Status  ...
      Put_Line ... Match_Status
      Put_Chess_Board ...
      --
      if App_State.Match_Status /= PLAYING then exit; end if;
   end loop;
end Main;
alr run

Choose your Symbol ...
   1. Letter 'X'
   2. Letter 'O'
Your choice: 1
You've chosen: X

+ - - + - - + - - +
|  2  |  9  |  4  |
+ - - + - - + - - +
|  7  |  5  |  3  |
+ - - + - - + - - +
|  6  |  1  |  8  |
+ - - + - - + - - +
Your move: 5

PLAYING ...
+ - - + - - + - - +
|  2  |  9  |  4  |
+ - - + - - + - - +
|  7  |  X  |  3  |
+ - - + - - + - - +
|  6  |  1  |  8  |
+ - - + - - + - - +
Computer move: 2

PLAYING ...
+ - - + - - + - - +
|  O  |  9  |  4  |
+ - - + - - + - - +
|  7  |  X  |  3  |
+ - - + - - + - - +
|  6  |  1  |  8  |
+ - - + - - + - - +
Your move: 1

PLAYING ...
+ - - + - - + - - +
|  O  |  9  |  4  |
+ - - + - - + - - +
|  7  |  X  |  3  |
+ - - + - - + - - +
|  6  |  X  |  8  |
+ - - + - - + - - +
Computer move: 4

PLAYING ...
+ - - + - - + - - +
|  O  |  9  |  O  |
+ - - + - - + - - +
|  7  |  X  |  3  |
+ - - + - - + - - +
|  6  |  X  |  8  |
+ - - + - - + - - +
Your move: 9

USER_WIN ...
+ - - + - - + - - +
|  O  |  X  |  O  |
+ - - + - - + - - +
|  7  |  X  |  3  |
+ - - + - - + - - +
|  6  |  X  |  8  |
+ - - + - - + - - +

Như vậy là với logic đơn thuần là ưu tiên chọn các ô cờ có nhiều tiềm năng thì Computer vẫn còn vài bước đi nữa mới chạm tới các ô cờ ít tiềm năng. Và nếu User chọn một đường thẳng đi qua tâm bàn cờ thì kết quả của ván cờ sẽ mất quân bình, do chúng ta chưa tạo logic để ưu tiên việc chặn bước đi kết thúc đường thẳng Winning_Move.

(chưa đăng tải) [Procedural Programming + Ada] Bài 23 – Console Tic-Tac-Toe App (tiếp theo)

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 đầ