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ề 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
.
[Procedural Programming + Ada] Bài 23 - Console Tic-Tac-Toe App (tiếp theo)