Tải bản đầy đủ (.docx) (26 trang)

báo cáo môn trí tuê nhân tạo áp dụng thuật toán tìm kiếm minmax và cắt tỉa alpha beta xây dựng trò chơi cờ cờ tướng trên ngôn ngữ java

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (466.43 KB, 26 trang )


ĐẠI HỌC BÁCH KHOA HÀ NỘI
VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG


BÁO CÁO BÀI TẬP LỚN
TRÍ TUỆ NHÂN TẠO
Đề tài:
Tìm hiểu và cài đặt thuật toán
MinMax và AlphaBeta vào game Cờ Tướng
Giảng viên: Phạm Văn Hải
Sinh viên nhóm 18:
1. Phạm Toàn Thắng 20102749
2. Nguyễn Viết Hiện 20101536
3. Nguyễn Đình Tài 20102117
4. Tô Đông Hoàng 20081074
5. Đỗ Đức Huy 20081125
6. Đỗ Văn Tùng 20102471
MỤC LỤC

I. Lời nói đầu
Các chiến lược tìm kiếm cơ bản chỉ sử dụng các thông tin chung của bài toán,
nó không phù hợp với nhiều bài toán thực tế trong cuộc sống vì chúng đòi hỏi quá
nhiều về thời gian và bộ nhớ. Bên cạnh đó, chúng ta ngày càng đặt ra những bài toán
trong thực tế, nhằm giải quyết được các vấn đề, nhu cầu của con người, với một lượng
thông tin, dữ liệu khổng lồ, cần phải đưa ra những chiến lượng giải quyết tối ưu, thông
mình để đạt hiệu quả hơn, vậy nên cần nghiên cứu, thiết lập và cải thiện những chiến
lược tìm kiếm với tri thức bổ sung (informed search strategies) sử dụng các tri thức cụ
thể của bài toán.
Trong đồ án này, nhóm sinh viên chúng em đã chọn ra một trong các giải thuật
tìm kiếm để mô phỏng vào một bài toán thực tế đó là chương trình “Cờ tướng”, một trò


chơi rất quen thuộc với chúng ta.
Với mục tiêu đặt là có thể hiểu và vận dụng được giải thuật tìm kiếm tri thức, và
củng cố được kĩ năng lập trình để tạo nên bản demo hoàn chỉnh.
Như đã nói ở báo cáo bài tập tuần, với thời lượng một tháng học, tuần đầu để
định hướng đề tài bài tập tuần, với thời lượng 2 tuần và đề tài bài tập lớn với thời
lượng 3 tuần. thời gian rất hạn hẹp, vậy nên nhóm chúng em đã họp và đi đến thống
nhất cách chia công việc cho nhóm 6 người như sau.
1. Nhóm 1 gồm 3 thành viên:
- Tô Đông Hoàng 20081074
- Đỗ Đức Huy 20081125
- Đỗ Văn Tùng 20102471
Có kĩ năng đọc hiểu tiếng anh tốt phụ trách chính việc nghiên cứu đề tài bài
báo, tóm tắt và phổ biến đến các thành viên còn lại của nhóm 2.
2. Nhóm 2 gồm 3 thành viên:
- Nguyễn Viết Hiện 20101536
- Phạm Toàn Thắng 20102749
- Nguyễn Đình Tài 20102117
Có kĩ năng lập trình tốt về ngôn ngữ Java, phụ trách chính về việc nghiên cứu
giải thuật cắt tỉa Alpha Beta để cài đặt thành công bản Game Demo – Cờ tướng.
Lời cuối chúng em xin gửi lời cám ơn đến
Phạm Văn Hải
đã trực tiếp hướng
dẫn và giúp chúng em có những kiến thức về trí tuệ nhân tạo, để có thể hoàn thành
tốt đồ án này. Tuy đã nỗ lực hết sức, nhưng đồ án vẫn còn những thiếu sót, chúng em
rất mong được thầy cũng như các bạn sinh viên góp ý xây dựng.
Chúng em xin chân thành cảm ơn.
3
II. Thuật toán MiniMax và AlphaBeta
1. Thuật toán MiniMax
1.1. Mô tả

Giả sử chúng ta có một bộ phân tích thế cờ có thể áp dụng tất cả các luật, các
phương pháp đánh cờ khác nhau vào từng thế cờ và chuyển đổi chúng thành một con
số đại diện (cho điểm thế cờ). Mặt khác, ta giả sử con số đó là dương khi áp dụng cho
thế cờ của một đấu thủ (được gọi là
người chơi cực đại
- maximizer), và là âm khi áp
dụng cho đấu thủ bên kia (được gọi là
người chơi cực tiểu
- minimizer). Quá trình tính
toán cho điểm thế cờ được gọi là
lượng giá tĩnh
(static evaluation). Hàm thực hiện việc
tính toán được gọi là một bộ lượng giá tĩnh, và giá trị nhận được gọi là điểm lượng giá
tĩnh. Cả hai đấu thủ đều cố gắng đi như thế nào đó để đạt được điểm tuyệt đối lớn
nhất. Người chơi cực đại sẽ tìm những nước đi dẫn đến điểm của mình trở nên lớn hơn
(hay cao nhất có thể được) hay điểm của đối thủ bớt âm hơn (nhỏ hơn về giá trị tuyệt
đối). Còn đấu thủ của anh ta, người chơi cực tiểu, lại ra sức phản kháng lại, để dẫn tới
điểm âm của anh ta âm hơn hay điểm dương của đối thủ nhỏ đi (hình 1).
4
Ví dụ một phần cây trò chơi trong hình 2
Người chơi cực đại hi vọng chọn nước đi bên phải để đạt được điểm 8. Thế
nhưng nếu đi như vậy thì khi đến lượt đi của người chơi cực tiểu, anh ta sẽ cố gắng
không cho người chơi cực đại đạt được điểm này bằng cách chọn nước đi nhánh bên
trái và như vậy, người chơi cực đại chỉ được có 1 điểm thay vì 8. Ngược lại, nếu người
chơi cực đại chọn nước đi bên trái, thì trong tình huống xấu nhất anh ta vẫn còn được
2 điểm, lớn hơn là chọn nước đi bên phải. Nói chung, người chơi cực đại sẽ phải tìm
cách nhận ra các nước đi của đối phương tiếp theo làm cho điểm giảm xuống. Và
tương tự như vậy, người chơi cực tiểu phải nhận biết được nước đi của người chơi cực
đại cố gắng làm tăng điểm lên. Thủ tục tìm nước đi tốt nhất trên cây trò chơi như trên
được gọi là thủ tục Minimax do điểm ở mỗi nút có thể là điểm cực đại hoặc có thể là

điểm cực tiểu và có thuật toán như sau:
5
Thuật toán Minimax
Nếu như đạt đến giới hạn tìm kiếm (đến tầng dưới cùng của cây tìm kiếm), tính
giá trị tĩnh của thế cờ hiện tại ứng với người chơi ở đó. Ghi nhớ kết quả
Nếu như mức đang xét là của người chơi cực tiểu, áp dụng thủ tục Minimax này
cho các con của nó. Ghi nhớ kết quả nhỏ nhất
Nếu như mức đang xét là của người chơi cực đại, áp dụng thủ tục Minimax này
cho các con của nó. Ghi nhớ kết quả lớn nhất.
1.2. Xây dựng chương trình cho thuật toán Minimax
Dựa vào phát biểu trên để viết chương trình cho thuật toán này bằng ngôn ngữ
tựa Java. Đây là một phương thức có tên là Minimax và sẽ là loại đệ qui. Trước hết, để
phương thức này biết đã đạt đến giới hạn tìm kiếm chưa, ta cần cung cấp cho nó một
tham số về độ sâu tìm kiếm
depth
(để biết phải tìm đến đâu). Giá trị trả về của hàm
chính là điểm của thế cờ (bàn cờ)
pos
.
Mỗi khi Minmax được gọi, nó sẽ càng gần đến giới hạn tìm kiếm, do đó ta sẽ gọi
hàm này với độ sâu bằng độ sâu cũ trừ đi một. Đạt đến độ sâu giới hạn chính là khi
depth
= 0. Khi đạt độ sâu này ta sẽ gọi phương thức lượng giá
Eval
để đánh giá chất
lượng của thế cờ
pos
hiện tại (thực hiện điều một của thuật toán). Như vậy bước đầu
phương này có dạng sau:
Public int MinMax (int pos, int depth){

if (depth == 0)
//Đã đạt đến giới hạn
Return Eval (pos)
//Tính giá trị thế cờ pos
else{

MinMax (pos, depth - 1);
//Gọi đệ qui với độ sâu giản dần



}
}
Ở trên, Minmax được gọi với độ sâu giảm đi một. Đó là độ sâu của các thế cờ là
con. Các thế cờ con
pos
' đó là các thế cờ được tạo ra từ
pos
bằng cách đi một nước đi
hợp lệ
m
nào đó. Do đó ta phải có các lệnh thực hiện đi quân để đến các thế cờ mới.
Để biết từ thế cờ
pos
có thể đi được những nước nào, ta dùng một thủ tục
Gen

tham số là thế cờ cha
pos
. Thủ tục này sẽ cất các thế cờ con

pos'
đó vào bộ nhớ (dạng
danh sách). Việc tiếp theo là ta lấy từng thế cờ đó ra và áp dụng tiếp thủ tục Minimax
cho nó để tính điểm value của nó.
Vậy phương thức MinMax có dạng:
6
public int MinMax (int pos, int depth){
if (depth == 0)
return Eval (pos)
// Tính giá trị thế cờ pos
else{
Gen (pos);
//Sinh ra mọi nước đi từ thế cờ pos
while (còn lấy được một nước đi m){
pos = Tính thế cờ mới nhờ đi m;
value = Minimax (depth-1);
//Tính điểm của pos

}

}
}
Theo phát biểu của thuật toán, ta thấy các điều 2 và 3 chỉ khác nhau ở cách
chọn kết quả tốt nhất
best
phụ thuộc vào người chơi đang là người chơi cực đại hay
cực tiểu. Cuối cùng thuật toán sẽ trả về điểm tốt nhất đạt được. Vậy hàm này được
phát triển tiếp thành:
public int MinMax (int pos, int depth){
if (depth == 0){

return Eval (pos)
//Trả về giá trị thế cờ pos
}
else{
Gen (pos);
//Sinh ra mọi nước đi từ thế cờ pos
while (còn lấy được một nước đi m)
{
pos = Tính thế cờ mới nhờ đi m;
value = Minimax (pos, depth-1);
//Tính điểm của pos
// Chọn điểm tốt nhất tuỳ thuộc theo người chơi
if (người chơi là người cực đại) {
if (value > best) best = value;
}
}
return best;
// Trả về giá trị tốt nhất
}
Thông thường để cho tiện (và cũng rất gần sự thực) ta coi cả hai người chơi
(hai bên) có cùng cách đánh giá về một thế cờ. Có điều thế cờ này là tốt với một người
thì phải được đánh giá là tồi với người kia và ngược lại. Trong máy tính cách thể hiện
tốt nhất là ta cho điểm một thế cờ có thêm dấu âm dương: dấu dương dành cho người
chơi cực đại và dấu âm cho người chơi cực tiểu. Với người chơi cực đại sẽ mong muốn
điểm này càng dương càng tốt, còn người chơi cực tiểu lại mong muốn điểm này càng
âm càng tốt. Do đó để dễ xử lí ta sẽ tuỳ theo mức người chơi mà đổi dấu giá trị đánh
giá thế cờ
pos
. Chú ý rằng, thay đổi độ sâu là chuyển sang đối phương nên phải đổi
dấu. Chương trình thực hiện đổi dấu như sau:

7
value = -Minimax (depth-1); //
Tính điểm của pos
Cũng do dùng cùng hàm lượng giá nên khi đến lượt người chơi cực đại và cực
tiểu có cùng cái nhìn như nhau về một thế cờ. Điều này dẫn đến có thể dùng cùng
cách chọn nước đi tốt nhất cho họ (gộp được điều 2 và 3 lại với nhau được). Giá trị
best cần được khởi đầu rất nhỏ để đảm bảo không vượt mọi giá trị value, tốt nhất là
giá trị - vô cùng:
Public int MinMax (int pos, int depth){
if depth = 0 then
return Eval (pos);
//Trả về giá trị thế cờ pos

else{
best = -INFINITY;
Gen (pos);
//Sinh ra mọi nước đi từ thế cờ pos

while (còn lấy được một nước đi m)
{
pos = Tính thế cờ mới nhờ đi m;
value = -MinMax (pos, depth - 1);
if (value > best) best = value;
}
return best;
}
}
Thông thường, bàn cờ được biểu diễn bằng các biến toàn cục. Do đó thay cho
truyền tham số là một bàn cờ mới pos vào thủ thục Minimax thì người ta biến đổi luôn
biến toàn cục này nhờ thực hiện nước đi "thử" (nước đi dẫn đến bàn cờ mới

pos
). Sau
khi Minimax thực hiện việc tính toán dựa vào bàn cờ lưu ở biến toàn cục thì thuật toán
sẽ dùng một số thủ tục để loại bỏ nước đi này. Minimax bỏ các tham số
pos
và được
xây dựng hoàn chỉnh như sau:
public int MinMax(int depth) {
if (!run) {
return -1;
}
int best, i, value;
if (depth == 0) {
return Eval();
} else {
best = -INFINITY;
Gen();
i = gen_begin[ply];
8
while (i < gen_end[ply]) {
makeMove(gen_dat[i].m);
value = MinMax(depth - 1);
unMakeMove();
if (value > best) {
best = value;
if (ply == 0) {
newMove = gen_dat[i].m;
}
}
++i;

}
return best;
}
}
1.3. Đánh giá
Nếu hệ số nhánh trung bình của cây là
b
và ta thực hiện tìm kiếm đến độ sâu
d
thì số nút phải lượng giá ở đáy cây như ta đã biết là
b
d
. Đây chính là số đo độ phức tạp
của thuật toán. Nếu
b
= 40,
d
= 4 (các con số thường gặp trong trò chơi cờ) thì số nút
phải lượng giá là 40
4
= 2560000 (trên 2 triệu rưỡi nút). Còn với
b
= 40,
d
= 5 thì số
nút phải lượng giá sẽ tăng 40 lần nữa thành 40
5
= 102400000 (trên 102 triệu nút).
Lưu ý: toàn bộ ý tưởng của thuật toán này là dựa trên việc chuyển đổi mỗi thế
cờ thành một con số để đánh giá. Rất tiếc là các con số này thường không tốt và

không đủ để đánh giá hết mọi điều. Mặt khác, thuật toán này có thể rất tốn kém (chạy
chậm) do việc sinh các nước đi và lượng giá rất tốn thời gian tính toán, do vậy độ sâu
của cây trò chơi cũng bị hạn chế nhiều. Ta cần có thêm những cải tiến để cải thiện tình
hình.
2. Thuật toán cắt tỉa AlphaBeta
2.1. Mô tả
Thủ tục AlphaBeta là một cải tiến thuật toán Minimax nhằm tỉa bớt nhánh của
cây trò chơi, làm giảm số lượng nút phải sinh và lượng giá, do đó có thể tăng độ sâu
của cây tìm kiếm. Giả sử hình 3 là một thế cờ mà hai nút đầu tiên đã được lượng giá.
Nếu thực hiện thủ tục Minimax đối với các nút đó sẽ cho thấy người chơi cực đại đã
được đảm bảo nếu đi nước bên trái sẽ được ít nhất là 2 điểm dù là các lượng giá của
các nút khác cho kết quả như thế nào đi nữa.
9
Bây giờ, ta lại giả sử nút tiếp theo được lượng giá và cho kết quả là 1. Nếu đi
vào nhánh này thì đối phương sẽ đảm bảo làm điểm của người chơi cực đại không thể
vượt quá được giá trị 1 dù là các lượng giá của các nút khác cho kết quả như thế nào
đi nữa. Do đó đến đây, nước đi tốt nhất là chọn nước đi bên trái với đảm bảo là ít nhất
đạt được 2 điểm. Và do đó, hoàn toàn không cần thiết phải lượng giá nút còn lại.
Nguyên tắc Alpha-Beta
Nếu biết điều đó thật sự tồi thì đừng mất thời gian tìm hiểu nó sẽ tồi
tệ đến đâu
Ý tưởng này được gọi là nguyên tắc Alpha-Beta do nó dùng trong thủ tục
AlphaBeta (ta sẽ xét dưới đây). Hai tham số của thủ tục này (theo các đặt tên truyền
thống) được gọi là alpha và beta và dùng để theo dõi các triển vọng - chúng cho biết
các giá trị nằm ngoài khoảng [alpha, beta] là các điểm "thật sự tồi" và không cần phải
xem xét nữa. Khoảng [alpha, beta] còn được gọi là cửa sổ alpha, beta. Trong ngữ cảnh
của các trò chơi, nguyên tắc Alpha-Beta nói rằng, mỗi khi xem xét một nút bất kì, nên
kiểm tra các thông tin đã biết về các nút cha, ông của nó. Rất có thể do có đủ thông
tin từ cha, ông nên không cần phải làm bất cứ việc gì nữa cho nút này. Cũng vậy,
nguyên tắc này cũng giúp chỉnh sửa hoặc xác định chính xác giá trị tại nút cha, ông

nó. Như trên nói, một cách để tiện theo dõi quá trình tính toán là dùng các tham số
alpha và beta để ghi lại các thông tin theo dõi cần thiết. Thủ tục AlphaBeta được bắt
đầu tại nút gốc với giá trị của alpha là -vôcùng và beta là +vôcùng. Thủ tục sẽ tự gọi
đệ quy chính nó với khoảng cách giữa các giá trị alpha và beta ngày càng hẹp hơn.
10

Thuật toán AlphaBeta
*
• Nếu như đạt đến giới hạn tìm kiếm (đến tầng dưới cùng của cây tìm
kiếm), tính giá trị tĩnh của thế cờ hiện tại ứng với người chơi ở đó.
Ghi lại kết quả
*
*
Nếu như mức đang xét là của người chơi cực đại,
o Thực hiện các công việc sau cho đến khi tất cả các con của
nó đã được xét với thủ tục AlphaBeta hoặc cho đến khi
alpha là bằng hoặc lớn hơn beta.
- Áp dụng thủ tục AlphaBeta với giá trị alpha và
beta hiện tại cho một con. Ghi nhớ lại kết quả.
- So sánh giá trị ghi nhớ với giá trị alpha, nếu giá
trị đó lớn hơn thì đặt alpha bằng giá trị mới này.
o Ghi nhớ lại alpha.
11
2.2. Xây dựng chương trình cho thuật toán AlphaBeta
Từ phát biểu trên ta sẽ xây dựng hàm AlphaBeta bằng ngôn ngữ tựa Pascal.
Hàm này sẽ có dạng khai báo như dưới, trong đó depth là độ sâu tìm kiếm, INFINITY
là giá trị vô cùng, thuật toán tính toán dựa trên thế cờ hiện tại pos là các biến toàn
cục:
public int AlphaBeta(int alpha, int beta, int depth) {
if (!run) {

return 0;
}
int best, value, i;
if (depth == 0) {
return Eval();
} else {
Gen();
best = -INFINITY;
i = gen_begin[ply];
while (i < gen_end[ply] && best < beta) {
if (best > alpha) {
alpha = best;
}
if (makeMove(gen_dat[i].m)) {
value = 1000 - ply;
} else {
value = -AlphaBeta(-beta, -alpha, depth - 1);
}
unMakeMove();
if (value > best) {
best = value;
if (ply == 0) {
newMove = gen_dat[i].m;
}
12
}
++i;
}
return best;
}

}
Cũng tương tự như thuật toán Minimax ta đã gộp hai mục 2 và 3 làm một nhờ
việc đổi dấu thích hợp. So với thuật toán Minimax thì trong thuật toán AlphaBeta đã
đưa thêm hai biến alpha, beta làm hai mức ngưỡng. Ta thấy cứ mỗi khi best >= beta
thì thuật toán không thực hiện tiếp vòng lặp, có nghĩa là nó không chịu mở rộng tiếp
những nhánh còn lại nữa. Các nhánh đó đã bị cắt bỏ - và do đó ta sẽ tiết kiệm được
thời gian. Việc cắt bỏ này hoàn toàn an toàn với những lí do ta đã xét ở trên. Ta thấy
rằng mỗi lần hàm này được gọi thì chỉ có tham số beta được dùng để so sánh cắt bỏ,
còn tham số alpha không được dùng. Tuy nhiên khi áp dụng cùng thuật toán cho cây
con thì ta đã hoán vị hai giá trị alpha, beta cho nhau (và đảo cả dấu), do đó alpha sẽ
có tác dụng trong độ sâu sau, rồi độ sâu sau nữa lại đến lượt beta Nói cách khác,
một giá trị chỉ luôn ảnh hưởng đến người chơi cực đại, còn giá trị kia lại luôn ảnh
hưởng đến người chơi cực tiểu. Chúng là các ngưỡng của họ (ngưỡng giữa các nước đi
được chấp nhận và không chấp nhận). Những nước đi cần quan tâm phải nằm lọt giữa
hai giá trị này. Dần dần khoảng cách giữa hai giá trị alpha - beta càng ngày càng thu
hẹp và dẫn đến các nhánh cây có giá trị nằm ngoài khoảng này nhanh chóng bị cắt bỏ
(hình 4).
2.3. Đánh giá
Trong điều kiện lí tưởng, thuật toán AlphaBeta chỉ phải xét số nút theo công
thức:
=
với
d
chẵn
13
=

với
d
lẻ


Với
b
= 40 và
d
= 4 ta có số nút phải xét là 2x40
2
- 1 = 3199. Như vậy trong
điều kiện lí tưởng thì số nút phải xét nhờ AlphaBeta (chỉ khoảng 3 nghìn nút) ít hơn
thuật toán Minimax (hơn 2,5 triệu nút) là 2560000 / 3199 khoảng 800 lần. Còn với
b
=
40 và
d
= 5 ta có số nút phải xét là 40
3
+ 40
5/2
- 1 = 64000+10119-1 = 74118. Số nút
phải xét nhờ AlphaBeta ít hơn thuật toán Minimax (hơn 102 triệu nút) là
102400000/74118 = 1382 lần.
Dưới đây là bảng so sánh số nút phải xét giữa hai thuật toán Minimax và
AlphaBeta.
Độ sâu
Minimax AlphaBeta
Tỉ lệ số
nút
Minimax /
AlphaBeta
Số nút Số lần tăng Số nút Số lần tăng

1 40 40 1
2 1600 40 79 1.9 20
3 64000 40 1852 23.2 34
4 2560000 40 3199 1.7 800
5 102400000 40 74118 23.2 1381
6 4096000000 40 127999 1.7 32000
7 163840000000 40 2964770 23.2 55262
8 6553600000000 40 5120000 1.7 1280000
Ta có thể nhận xét như sau:
• Số lần tăng số nút khi tăng độ sâu của Minimax luôn là hệ số phân nhánh
b
,
trong trường hợp này là 40. Số lần tăng của AlphaBeta ít hơn nhiều: chỉ cỡ 1.7
lần khi tăng từ
d
lẻ sang
d
chẵn và 23.2 lần khi từ
d
chẵn sang lẻ - trung bình
chỉ tăng khoảng hơn 6 lần khi tăng
d

• Số nút của AlphaBeta tăng chậm hơn rất nhiều lần so với Minimax. Tỉ số nút
phải xét giữa hai thuật toán này càng cao khi
d
càng lớn.
14
Công thức tính số nút cho thấy số nút phải xét khi dùng AlphaBeta ít hơn nhiều
so với Minimax nhưng vẫn là hàm số mũ và vẫn dẫn tới bùng nổ tổ hợp. Thuật toán

AlphaBeta hoàn toàn không chống được bùng nổ tổ hợp mà chỉ làm giảm tốc
độ bùng nổ. Tuy trong thực tế số nút phải xét (lượng giá) thường nhiều hơn trong
điều kiện lí tưởng nhưng nó vẫn đủ để tiết kiệm khá nhiều thời gian. Trong cùng một
khoảng thời gian, thuật toán AlphaBeta có thể tìm đến độ sâu gấp hai lần độ sâu tìm
kiếm bằng Minimax. Hình 5 là đồ thị so sánh giữa hai thuật toán này.
Ví dụ: Ta sẽ xem xét thuật toán AlphaBeta hoạt động như thế nào đối với cây
trò chơi như trong hình 6.
15
Cây này có độ sâu bằng 3 và hệ số phân nhánh bằng 3. Các thứ tự kết luận (các
con số bên trái) được đưa ra như sau:
1-2 Tìm kiếm đi xuống dưới theo nhánh trái cho đến lá. Ở đây giá trị tĩnh thu
được là 8. Giá trị đầu tiên này do người chơi cực đại được phép chọn trong ba
giá trị ở nhánh này đã đảm bảo rằng là kết quả thu được sẽ ít nhất là bằng 8.
Điều lưu ý này được bước 2 ghi lại.
3-5 Để chắc chắn không còn có điểm nào cao hơn 8, người chơi cực đại phải
xét cả hai thế cờ còn lại và thu được các giá trị 7 và 2. Do đó đến đây đã kết
luận chính xác điểm cao nhất có thể đạt được ở cây con là đúng bằng 8.
6. Leo lên một tầng cây. Đây là các nước đi của người chơi cực tiểu. Ta
không hi vọng anh ta cho người chơi cực đại được nhiều điểm nên có thể tạm
kết luận ở mức này là sẽ đạt được nhiều nhất là 8 điểm.
7-8. Để xem người chơi cực tiểu còn lựa chọn nào tốt hơn (và tồi tệ hơn cho
người chơi cực đại) ta phải xem xét cả hai nước đi còn lại. Nước đi còn lại đầu
tiên dẫn đến giá trị lượng giá tĩnh là 9 - một giá trị lớn hơn 8. Như vậy nhánh
giữa là tồi tệ hơn cho người chơi cực tiểu. Đến đây việc cắt bỏ được thực hiện
- đừng hòng người chơi cực đại với tới được điểm đó khi đã có sẵn lựa chọn
thấp hơn cho anh ta (là 8). Điều này cũng dẫn đến không cần thiết phải xét
hai nút còn lại - đằng nào nhánh giữa cũng đủ tồi tệ rồi và người chơi cực tiểu
sẽ không chọn nó để đi.
9-14. Người chơi cực tiểu cần phải khảo sát tiếp lựa chọn cuối cùng. Cách làm
tương tự như phần trên. Ở đây phải lượng giá cả ba nút cây và kết luận cuối

cùng được đưa ra là người chơi cực đại đi giỏi lắm thì chỉ đạt được 4 điểm.
15. Như vậy nhờ việc khảo sát nhánh cây bên phải người chơi cực tiểu thấy
rằng nếu chọn đi theo nhánh này thì người chơi cực đại chỉ được có 4 điểm
thay cho 8.
16. Bây giờ ta có thể kết luận ở mức trên cùng. Mức này là của người chơi
cực đại. Anh ta thấy rằng nếu chọn đi theo nhánh trái thì được 4 điểm. Như
vậy anh ta đã chắc chắn điểm của mình sẽ ít nhất là 4 rồi. Để xem liệu có thể
đạt được điểm cao hơn nữa hay không cần phải xem xét hai nhánh còn lại.
17-30. Tương tự như phần trên, ta kết luận nhánh giữa sẽ mang lại cho người
chơi cực đại 5 điểm. 31. Cũng tương tự như kết luận 16, ở đây ta kết luận khả
quan hơn là người chơi cực đại đã cầm chắc 5 điểm và có thể còn cao hơn.
32-38 Ta kết luận được rất nhanh là cây con bên phải chỉ cho "thu hoạch"
nhiều nhất là 3 điểm - một điểm số quá kém do đó thuật toán không buồn
xem xét các trường hợp còn lại nữa. Do đó đã tiết kiệm được 6 nút không cần
phải lượng giá và cũng không phải sinh nước đi cho hai trường hợp.
39. Kết luận cuối cùng là điểm cao nhất mà người chơi cực đại có thể thu
được là 5 điểm nhờ chọn đi theo nhánh giữa.
3. Chương trình cờ tướng áp dụng thuật toán Minimax
16
Trò chơi Cờ Tướng (tên phiên âm Trung Quốc XiangQi, têntiếng Anh Chinese
Chess) là một minh hoạ rất tốt cho bài toán tìm kiếm trên cây trò chơi và áp dụng
thuật toán Minimax và AlphaBeta trên cây này như thế nào. Đây là một trò chơi thú vị
và rất phổ biến ở Việtnam, châu Á cũng như trên toàn thế giới. Nó tạo cảm giác dường
như máy tính có thể suy nghĩ và đọ sức với con người (thực tế cho đến nay nó vẫn chỉ
tính toán mà thôi).
3.1. Mô tả ý tưởng
Game có những chức năng:
- Tạo bàn cờ, với các chế độ người đấu với người, người đấu với máy, máy đấu
với máy
- Trong trường hợp, người đấu với máy, chương trình cho phép người dùng có thể

tủy chỉnh độ sâu xét duyệt của thuật toán
- (Máy nhìn trước bao nhiêu nước đi), độ sâu càng cao, độ khó càng tăng và thời
gian máy tính toán càng lâu.
- Game có hiển thị thời gian tính toán, số lượng nút lượng giá, hệ số phân nhánh
- Cho phép người dùng chuyển qua lại giữa hai thuật toán Minimax và AlphaBeta
để thấy được sự khác biết về tốc độ tính toán.
3.2. Xây dựng chương trình
a. Lượng giá.
Đánh giá một thế cờ là một trong những nhiệm vụ quyết định chương trình chơi
cờ của bạn có là "cao thủ" hay không. Căn cứ vào một thế cờ máy sẽ gán cho nó một
điểm số (lượng giá tĩnh) để đánh giá độ tốt - xấu. Nhờ điểm này máy mới có thể so
sánh các thế cờ với nhau và biết chọn nước đi tốt nhất. Đây là một nhiệm vụ rất khó
khăn và phức tạp do không tồn tại một thuật toán tổng quát và thống nhất nào để tính
điểm cả. Điểm của một thế cờ dựa trên rất nhiều yếu tố mà khó có thể số hoá hết
được như phụ thuộc vào số lượng và giá trị các quân cờ hiện tại, phụ thuộc vào tính
hãm, tính biến, thế công, thế thủ của từng quân cờ cũng như cả cục diện trận đấu. Ví
dụ, một cặp Mã giao chân, có thể sát cánh tiến quân và tựa lưng phòng thủ thường có
giá hơn hai Mã đứng rời. Nhưng cũng có lúc hai Mã đứng rời lại tốt hơn hai Mã giao
chân khi Mã này lại cản Mã kia trong một thế trận nào đó. Ta cũng biết rằng, "lạc nước
hai Xe đành bỏ phí, gặp thời một Tốt cũng thành công", thế nhưng số hoá điều này
qua hàm lượng giá quả là một điều khó quá sức.
Chúng ta bắt đầu với việc công nhận các giả thuyết sau:
1. Ta có thể biểu diễn chất lượng một thế cờ bằng một con số. Ví dụ, con số đó
có thể là đánh giá của ta về xác suất chiến thắng, nhưng đối với đa số
chương trình thì con số đó không có gì đặc biệt, nó chỉ là con số mà mục
đích chính là so sánh được với nhau.
2. Chúng ta nên đo chất lượng của một thế cờ tương tự như phép đo của đối
phương (do đó, nếu ta coi là đạt được một thế tốt thì đối phương của ta phải
thấy đó là thế xấu đối với họ và ngược lại). Điều này tuy không thật đúng
lắm, nhưng nó giúp cho thuật toán tìm kiếm của ta làm việc tốt và trong

thực tế cũng khá gần với sự thật.
17
Trong chương này chúng ta chỉ cài đặt phương pháp đơn giản nhưng cơ bản
nhất: lượng giá dựa trên cơ sở giá trị của từng quân cờ. Cách tính này sẽ lấy tổng giá
trị các quân cờ hiện có của bên mình trừ đi tổng giá trị các quân cờ hiện có của đối
phương. Do đó, một thế cờ này hơn thế cờ kia ở chỗ nó còn nhiều quân bên mình hơn,
nhiều quân giá trị cao hơn cũng như có bắt được nhiều quân và quân giá trị cao của
đối phương hơn không.
Cách làm này đã bỏ qua mất những nghệ thuật, chiến lược chơi cờ (mà đó là
những điểm để đánh giá một người là giỏi cờ hay không). Các quân cờ được triển khai
không theo một chiến lược chung nào hết (vô định). Nó còn tạo cảm giác như cờ "vồ",
nghĩa là chỉ nhằm nhằm sơ hở của đối phương là "ăn" ngay mà không cần biết đến
hậu quả (điều này không hẳn đúng, lí do sẽ trình bầy dưới). Tuy nhiên phương pháp
tính điểm này có những ưu điểm sau:
• Là cách tính điểm cơ bản nhất, đóng vai trò chủ đạo trong điểm của một thế cờ. Nó
là cơ sở của đa số hàm đánh giá. Ta cũng có thể nhận thấy trong phần lớn thời gian
diễn ra trận đấu, hai bên đều tìm cách tiêu diệt quân của nhau. Các phương án,
chiến lược thường chỉ được tính như những điểm cộng thêm vào và đóng góp một
tỷ lệ nhỏ trong tổng số điểm của một thế cờ. Việc chỉ nhằm "vồ" quân của đối
phương nhưng sau khi suy nghĩ sâu nhiều nước cũng đã trở thành "cao cờ" rồi đấy.
Nói cho cùng, mục đích chính của chúng ta cũng là "vồ" bằng được quân có giá trị
cao nhất - Tướng đối phương.
• Là cách tính đơn giản nhất và nhanh nhất. Do tính nhanh, ta có thể tăng thêm độ
sâu tìm kiếm. Việc tăng thêm độ sâu lại giúp máy có cái nhìn xa hơn, "cao cờ" hơn
và nhiều khi khắc phục được nhược điểm của cách tính đơn giản.
Điểm các quân cờ được đánh giá theo kinh nghiệm và cho biết sự tương quan
giữa các quân cờ. Sau đây là điểm từng quân mà mọi người thường chấp nhận:
Quân cờ Kí hiệu Điểm
Tốt PAWN 1 (2 nếu đã qua sông)
Sĩ BISHOP 2

Tượng ELEPHANT 2
Mã KNIGHT 4
Pháo CANNON 4.5
Xe ROOK 9
Bạn cũng có thể theo bất kì thang điểm nào khác tuỳ theo hiểu biết và sở thích
của mình. Ví dụ như trong làng cờ Việt nam thường cho điểm các quân hơi khác (xem
bài đọc dưới): Tốt - 1 (2 nếu đã qua sông), Sĩ - 2, Tượng - 2.5, Mã - 4.5, Pháo - 5, Xe -
10.
Trong chương trình cờ của chúng ta, điểm cụ thể của từng quân cờ là các số nguyên
10, 20, 20, 40, 45 và 90. Ta dùng một mảng piecevalue để lưu điểm từng quân cờ. Cho
điểm như vậy không những vẫn giữ nguyên được tương quan và tránh dùng số thực
(tính chậm) mà ta còn có một khoảng cách hợp lí giữa các điểm để sau này dễ dàng
thêm những điểm bổ xung "thưởng" hoặc "phạt", tức là những điểm căn cứ vào những
18
yếu tố khác (ví dụ cùng là Pháo nhưng lúc chỉ được 40 điểm do ở vị trí "bí", lúc khác lại
có thể đến 85 do ở vị trí Pháo trống và đang đe doạ Pháo lồng).
Trong bàn cờ, quân Tướng là quân quan trọng nhất, dù mất mọi quân hoặc đạt
được thế cờ nào đi nữa đều không được mất Tướng vì nó dẫn đến thua cờ. Do đó,
Tướng thường được cho một điểm rất cao, cách biệt nhiều lần so với các quân khác,
đảm bảo điểm tổng của các quân còn lại cùng đủ mọi loại "thưởng", "thêm" đều không
bằng được Tướng. Trong chương trình, ta cho nó 1000 điểm.
Hàm lượng giá thật đơn giản như sau (chú ý là ta chưa tăng điểm cho Tốt đã
qua sông):
function Eval: integer;
const piecevalue:array[1 7]of integer=(10,20,20,40,45,90,1000);
var i, s: integer;
begin
s := 0;
for i:=1 to BOARD_SIZE do begin
if color[i] = side then s := s + piecevalue[piece[i]]

else if color[i] = xside then s := s - piecevalue[piece[i]];
end;
Eval := s;
end;
Vấn đề cải tiến hàm lượng giá sẽ được bàn riêng trong một chương phần sau.
Tham khảo kiến thức cờ Tướng
GIÁ TRỊ CỦA CÁC QUÂN CỜ
Khái niệm giá trị của các quân cờ không phải là mới đối với những người chơi cờ từ các thế kỷ
trước. Từ lâu, làng cờ đã có câu nhận định sức mạnh của các quân chủ lực là "Xe 10, Pháo 7, Mã
3" hoặc đánh giá Xe là quân chủ lực mạnh nhất bằng câu "Nhất Xa sát vạn" hay đánh giá một Tốt
qua hà, sức mạnh bằng nửa con Xe (nhất Tốt độ hà, bán Xa chi lực). Thế nhưng sự đánh giá nhận
định này về sức mạnh của các quân không chính xác và cũng không đầy đủ. Do đó khái niệm giá
trị của các quân cần được xác định rõ và hoàn chỉnh hơn. Thông thường, theo thời gian một ván
cờ được chia thành ba giai đoạn: khai cuộc, trung cuộc và tàn cuộc. Trong ba giai đoạn này thì
trung cuộc chiếm nhiều thời gian nhất và có ý nghĩa đến thắng thua nhiều nhất. Do đó các định
giá quân cờ thường là cho giai đoạn này.
Chúng ta đã biết mỗi loại quân cờ hay mỗi binh chủng có đặc điểm, tính năng và tác dụng khác
nhau nên sức mạnh của chúng cũng khác nhau, giá trị của chúng cũng không giống nhau. Ngay
bản thân mỗi quân cờ cũng không phải lúc nào sức mạnh cũng giữ nguyên như cũ. Khi đứng ở vị
trí tốt nó phát huy tính năng tác dụng tối đa khác hẳn với khi nó đứng ở vị trí xấu, không thể phát
huy tính năng tác dụng được, hay chỉ phát huy ở mức rất hạn chế. Vậy điều trước tiên chúng ta
phải thấy mỗi quân cờ có hai giá trị: giá trị vốn có và giá trị biến động. Giá trị vốn có thường được
gọi là giá trị cơ bản (giá trị tĩnh), còn giá trị biến động được gọi là giá trị tương đối.
Các nhà nghiên cứu lí luận về cờ đã trao đổi thống nhất với nhau bảng giá trị cơ bản của các quân
như sau:
Nếu lấy Tốt chưa qua sông làm chuẩn để xác định giá trị thì nó là quân kém năng lực nhất trên
bàn cờ. Khi chưa qua sông nó chỉ kiểm soát hay khống chế mỗi một điểm trước mặt nó nên tạm
cho nó có giá trị bằng 1. Khi Tốt qua sông, nó khống chế đến 2 hay 3 điểm trước mặt và bên cạnh
(Tốt biên chỉ khống chế được 2). Bây giờ nó mang giá trị tương đối, phải thấy nó mạnh hơn lúc
chưa qua sông nên giá trị lúc này của nó phải bằng 2.

Đối với Sĩ, Tượng là loại binh chủng phòng ngự, có nhiệm vụ chính là bảo vệ an toàn cho Tướng,
đôi lúc chúng cũng trợ giúp các quân khác tấn công. Trong giai đoạn trung cuộc, Tốt đối phương
qua sông đổi được một Sĩ hoặc một Tượng. Do đó người ta đánh giá Sĩ hoặc Tượng có giá trị bằng
19
một Tốt đã qua sông, tức là bằng 2. Nhưng vì so sánh năng lực giữa Tượng và Sĩ thì thấy Tượng
có phần đắc lực hơn nên định giá trị Sĩ là 2 thì Tượng phải là 2.5 mới công bằng.
Quân Mã trên bàn cờ có khả năng khống chế tối đa 8 điểm. Mỗi bước nhẩy của nó vượt được hai
tuyến đường ngang hay dọc. So ra thì Mã cũng mạnh đấy nhưng tác chiến ở tầm cự ly ngắn mà
thôi. Nếu so với hai Tốt đã qua sông cặp kè nhau thì năng lực của Mã không hơn bao nhiêu, do đó
định giá trị cơ bản của Mã 4.5 là vừa. Nên nhớ hai Tốt cặp kè nhau khi qua sông phải có giá trị
hơn 4.
Pháo là binh chủng tác chiến tầm xa rất có hiệu quả. Trên đường dọc, Pháo khống chế tối đa chỉ 8
điểm nhưng nó còn khống chế được cả đường ngang. Mặt khác Pháo là quân có tính cơ động cao,
nó có thể đi một bước vượt đến 9 tuyến đường. Nhưng ở cự li gần, nhiều khi Pháo bất lực, không
làm gì được để khống chế đối phương. Do đó so với Mã thì phải thấy mỗi quân có một sở trường
riêng, sức mạnh của chúng coi như tương đương nhau. Thế nhưng trong giai đoạn trung cuộc
quân số của hai bên tương đối còn nhiều, Mã đi lại dễ bị cản trở, còn Pháo thì nhanh nhẹn hơn,
dễ phát huy năng lực hơn nên giá trị cơ bản của Pháo phải hơn Mã một chút và bằng 5.
Đối với Xe thì rõ ràng là một binh chủng cơ động mạnh nhất, nó khống chế ngang, dọc tối đa đến
17 điểm. Nếu đem Xe đổi lấy một Pháo và một Mã của đối phương thì thấy có phần thiệt thòi một
chút, còn đối với hai Pháo thì có thể coi là cân bằng. Do đó giá trị cơ bản của Xe là 10.
Còn Tướng, bản thân nó không có sức mạnh gì nhiều dù đôi khi nó cũng làm được nhiệm vụ trợ
giúp cho các quân phe nó tấn công có kết quả. Thế nhưng giá trị cơ bản của nó thì vô cùng to
lớn, không một quân nào so sánh được. Tất cả các quân đều có thể đổi, có thể hi sinh nhưng
riêng quân Tướng không thể đánh đổi mà cũng không thể hi sinh, vì mất Tướng là thua cờ. Do đó
cũng không nên định giá trị cơ bản của Tướng vì định cũng chẳng có ý nghĩa gì.
(Sưu tầm)

b. Sinh nước đi.
Một trong những việc quan trọng nhất để máy tính có thể chơi được cờ là phải

chỉ cho nó biết mọi nước đi có thể đi được từ một thế cờ. Máy sẽ tính toán để chọn
nước đi có lợi nhất cho nó. Các yêu cầu chính đối với một thủ tục sinh nước đi là:
• Chính xác (rất quan trọng). Một nước đi sai sẽ làm hỏng mọi tính toán. Đồng
thời chương trình có thể bị trọng tài xử thua luôn. Do số lượng nước đi sinh
ra lớn, luật đi quân nhiều và phức tạp nên việc kiểm tra tính đúng đắn tương
đối khó.
• Đầy đủ (rất quan trọng). Sinh được mọi nước đi có thể có từ một thế cờ.
• Nhanh. Do chức năng này phải sinh được hàng triệu nước đi mỗi khi máy
đến lượt nên giảm thời gian sinh nước đi có ý nghĩa rất lớn.
Sinh nước đi là một thuật toán vét cạn. Máy sẽ tìm mọi nước đi hợp lệ có thể có.
Máy phải sinh được từ những nước đi rất hay cho đến những nước đi rất ngớ ngẩn
(như đẩy Tướng vào vị trí khống chế của đối phương). Ta hình dung ngay thủ tục sinh
nước đi Gen sẽ có đầy những vòng lặp for, các câu lệnh kiểm tra if và rẽ nhánh case,
trong đó các phép tính kiểm tra giới hạn chiếm một phần đáng kể. Thủ tục này luôn
sinh nước cho bên đang tới lượt chơi căn cứ vào nội dung của biến side. Đây là một
trong những thủ tục phức tạp và dễ sai nhất.
20
Một nước đi có hai giá trị cần quan tâm. Đó là điểm xuất phát (from) và điểm
đến (dest). Ta sẽ khai báo một cấu trúc move như sau để dùng những nơi cần đến dữ
liệu nước đi.
type
move = record { Định nghĩa cấu trúc nước đi }
from, dest: byte;
end;


Kiểm tra giới hạn bàn cờ
Ví dụ có một quân Xe nằm ở ô số 84 (trong mảng piece). Bây giờ ta sẽ sinh thử
một nước đi sang trái một ô cho nó. Nước đi sang trái một ô được chuyển thành ô số
83 là một vị trí cùng hàng với ô xuất phát nên được chấp nhận. Một nước đi khác cần

phải xem xét là sang trái ba ô - ô 81. Ô 81 tuy có trong bàn cờ nhưng khác hàng nên
không được chấp nhận. Như vậy, muốn biết ô của nước đi sang trái có được phép
không ta phải kiểm tra xem nó có cùng hàng với ô xuất phát không. Việc kiểm tra thực
hiện bằng cách chia cho 9 (kích thước của một dòng) và lấy phần nguyên (trước đó lại
phải chuyển thứ tự ô về gốc là 0 bằng cách trừ đi 1). Ta thấy ((83-1) div 9) = ((84-1)
div 9) nên ô 83 được chấp nhận; trong khi đó do ((81-1) div 9) <> ((84-1) div 9) nên
kết luận là nước đi đến ô 81 không hợp lệ (hình 2.5).
21
Các nước đi vừa sinh ra sẽ được đưa vào danh sách nước đi nhờ gọi thủ tục
Gen_push. Thủ tục này có hai tham số là vị trí xuất phát của quân cờ sẽ đi và nơi đến
dest của quân cờ đó.
Dưới đây là đoạn chương trình dùng để sinh những nước đi sang trái của một
quân Xe, trong đó x là vị trí của quân Xe này .
i := x - 1; { Nước sang trái đầu tiên }
while ((i-1) div 9) = ((x-1) div 9) do
begin
if (ô thứ i là trống) or (ô thứ i có quân đối phương) then
gen_push(vị trí của Xe, vị trí ô đang xét);
if ô thứ i không trống then
break; { Kết thúc quá trình sinh nước đi sang trái }
i := i - 1; { Xét tiếp vị trí bên trái }
end;
Việc sinh những nước đi theo chiều khác cũng tương tự (ví dụ để sinh nước đi
theo chiều đứng ta chỉ cần cộng hoặc trừ một bội số của 9) nhưng điều kiện kiểm tra
sẽ khác nhau. Như vậy, chương trình sinh nước đi Gen có dạng như sau:
for Xét lần lượt từng quân cờ bên hiện tại do
case quâncờ of
Xe: begin
while Xét lần lượt tất cả các ô từ bên phải Xe cho đến lề trái bàn cờ do
begin

if (ô đang xét là ô trống) or (ô đang xét chứa quân đối phương) then
gen_push(vị trí của Xe, vị trí ô đang xét)
if ô đang xét không trống then break;
end;
while Xét lần lượt tất cả các ô từ bên trái Xe cho đến lề phải bàn cờ do

while Xét lần lượt tất cả các ô từ bên trên Xe cho đến lề trên bàn cờ do

while Xét lần lượt tất cả các ô từ bên dưới Xe cho đến lề dưới bàn cờ do

end; { Xong quân Xe }
Pháo:

Phương pháp này có nhược điểm là chương trình phải viết phức tạp, cồng kềnh, khó
tìm lỗi nhưng khá nhanh.
Trong chương trình mẫu VSCCP, chúng tôi giới thiệu một kĩ thuật khác có tên gọi
là "hộp thư" (mail box - do các bảng của nó có dạng các hộp phân thư). Mục đích
chính của kĩ thuật này là nhằm giảm bớt các phép kiểm tra vượt giới hạn bàn cờ và làm
đơn giản chương trình. Mấu chốt là thay cho bàn cờ có kích thước bình thường 9x10 =
90, ta dùng một bàn cờ mở rộng, mỗi chiều có thêm 2 đường nữa (bàn cờ mới có kích
22
thước 13x14 = 182). Các ô ứng với các đường bao mở rộng đều có giá trị -1, tức là các
giá trị báo vượt biên. Các nước đi trên bàn cờ 9x10 được chuyển tương ứng sang bàn
cờ này. Nếu một nước đi được sinh ra lại rơi vào một trong hai đường bao thì có nghĩa
nó đã rơi ra ngoài bàn cờ rồi, phải bỏ đi và ngừng sinh nước về phía đó. Sở dĩ có đến
hai đường biên (chứ không phải một) do quân Mã và Tượng có thể nhẩy xa đến hai ô
(hình 2.6).
Việc chuyển đổi toạ độ thực hiện nhờ hai mảng. Mảng mailbox90 dùng để
chuyển từ toạ độ bàn cờ thường sang toạ độ bàn cờ mới. Mảng ứng với bàn cờ mới -
mailbox182 dùng để kiểm tra các nước đi vượt quá đường biên và dữ liệu để chuyển

trở lại toạ độ bình thường.
Ví dụ, nếu vị trí quân Xe nằm ở ô số 84 (trong mảng piece) như hình 2.5, thì khi
đổi sang bàn cờ mở rộng sẽ thành vị trí mailbox90[84] = 148. Có nghĩa là nó ứng với ô
thứ 148 của mảng mailbox182. Bây giờ ta sẽ sinh thử một nước đi sang trái một ô cho
quân Xe này. Việc sinh và kiểm tra sẽ được thực hiện trong bàn cờ mở rộng, nước đi
mới là ô số 148 - 1 = 147. Do mailbox182[147] = 83 ¹ -1 nên nước đi này được chấp
nhận. Số 83 cho biết kết quả sang trái một ô là vị trí 83 trong bàn cờ bình thường. Tuy
nhiên, nước đi sang trái ba ô, có số 148 - 3 = 145 và mailbox182[145] = -1 cho biết
đó là một nước đi không hợp lệ.
Chương trình cũng cần kiểm tra số nước đi tối đa theo một hướng của quân cờ
đang xét. Chỉ có quân Pháo và Xe có thể đi đến 9 nước đi, còn các quân khác có nhiều
nhất là 1.
Việc đi một quân sang vị trí mới thực chất là ta đã thay đổi toạ độ của nó bằng
cách cộng với một hằng số (dương hoặc âm). Ở trên ta đã thấy để quân Xe sang trái
một nước ta đã phải cộng vị trí hiện tại với -1. Để sinh một ô mới theo hàng dọc, ta
phải cộng với +13 hoặc -13 (chú ý, việc sinh nước và kiểm tra hợp lệ đều dựa vào
mảng mailbox182 nên giá trị 13 là kích thước một dòng của mảng này). Để sinh các
nước đi chéo ta phải cộng trừ với một hằng số khác. Ta nên lưu các hằng số này vào
mảng offset có 2 chiều. Một chiều dựa vào loại quân cờ nên chỉ số được đánh từ 1 đến
7. Chiều còn lại là số hướng đi tối đa của một quân cờ. Quân cờ có nhiều hướng nhất
là quân Mã có tới 8 hướng nên chiều này được khai báo chỉ số từ 1 đến 8. Các quân cờ
có số hướng ít hơn sẽ được điền 0 vào phần thừa. Chú ý là nước đi quân Tốt của hai
bên khác nhau hướng tiến. Để tiết kiệm ta chỉ lưu nước tiến của Tốt đen, còn nước tiến
của Tốt trắng chỉ đơn giản lấy ngược dấu.
23
Kiểm tra vị trí được phép đến
Việc chuyển toạ độ từ bàn cờ thường sang bàn cờ mở rộng chỉ giúp ta loại bỏ
được các nước vượt quá biên. Ta còn phải kiểm tra một số giới hạn khác. Ví dụ như
Tướng và Sĩ không thể đi ra ngoài cung, Tượng chỉ được phép ở 7 điểm cố định phía
bên mình, Tốt chỉ được phép tung hoành trên đất đối phương, còn phía bên mình cũng

bị giới hạn ngặt nghèo một số ô Để thực hiện những phép kiểm tra này, ta dùng một
mảng là legalmove ứng với từng ô trên bàn cờ sẽ cung cấp các thông tin này. Để kiểm
tra một quân cờ có được phép ở đó không, ta dùng một mặt nạ bít Maskpiece mà tuỳ
theo từng quân sẽ có giá trị khác nhau. Nếu ở ô cần kiểm tra có bit trùng với mặt nạ
này được đặt là 1 thì quân cờ đó được phép đến ô đấy.

Ví dụ, ô số 3 có giá trị legalmove[3] = 5 (chuyển thành số nhị phân là
00000101) cho biết các quân cờ được phép đi đến đó là Tượng, Xe, Pháo, Mã.
Ngoài ra, ta còn phải kiểm tra nước bị cản (đối với Tượng và Mã) và xử lí cách
ăn quân của quân Pháo như một trường hợp đặc biệt. Như vậy, tổng thể sinh các nước
đi cho một quân cờ có dạng như sau:
p := piece[i]; { Sinh nước đi cho quân cờ p ở vị trí i }
for j := 1 to 8 do begin { Số hướng đi tối đa }
if offset[p,j] = 0 then break;
x:=mailbox90[i]; { Chuyển sang bàn cờ mở rộng}
if p in [ROOK, CANNON] then n := 9 else n := 1;
for t:=1 to n do { Số nước có thể đi theo một hướng}
begin
if (p=PAWN) and (side=LIGHT) then x := x - offset[p, j]
else x := x + offset[p, j]; { Nước đi mới }
y := mailbox182[x]; { Chuyển về toạ độ bình thường }
if side = DARK then t := y else t := 91-y;
if (y=-1) or { Ra ngoài lề ? }
((legalmove[t] and maskpiece[p])=0) { Được phép ở vị trí này không ? }
then break; { Thoát nếu nước đi không hợp lệ }
{ Kiểm tra nước cản với Tượng, Mã và xử lí Pháo ở đây }

24
Một vấn đề nữa là luật hai Tướng không được đối mặt trực tiếp với nhau. Việc
kiểm tra được thực hiện nhờ hàm kingface. Hàm sẽ trả lại giá trị true nếu nước vừa đi

gây hở mặt Tướng. Hàm này có thể được đặt trong thủ tục sinh nước Gen. Tuy nhiên
đơn giản hơn, ta đặt nó trong thủ tục gen_push, nếu hở mặt Tướng thủ tục này sẽ
không đưa nước đi đó
c. Giao diện chương trình.
3.3. Cài đặt chương trình
Chương trình cờ tướng được viết trên nền java, sử dụng IDE là NetBean. Toàn
bộ Project gồm Source Code các file liên quan được chứa trong thư mục China Chess
gửi kèm với báo cáo. Để sử dụng có thể chạy file ChineseChess.jar
III. Kết luận
1. Các vấn đề khó khăn và cách thức giải quyết.
Mặc dù đã áp dụng được thuật toán tìm kiếm Minimax và AlphaBeta vào để giải
quyết được bài toán, tuy nhiên trong quá trình làm việc nhóm đã gặp rất nhiều khó
25

×