Cách đơn giản nhất để biểu diễn một bàn cờ trong máy tính là ta dùng một mảng hai chiều, kích thước 9 x 10: BYTE piece[10,9]; Mảng trên hoạt động tốt nhưng có cái bất tiện là ta phải dùn
Trang 1KHOA CÔNG NGHỆ THÔNG TIN
BỘ MÔN KHOA HỌC MÁY TÍNH
Đề bài : Minh họa thuật toán Anpha – Beta bằng chương trình chơi cờ tướng
I Giới thiệu
Trang 2Trò chơi Cờ Tướng 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 AlphaBeta trên cây này như thế nào Đây là một trò chơi thú vị và tương đối phổ biến ở Việt nam, 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 Cờ Tướng là loại cờ có độ phức tạp vàrất nhiều mặt tương đương với cờ Vua.
Trong phần này, chúng tôi sẽ giới thiệu những kiến thức cơ bản nhất về một chương trìnhchơi cờ phải như thế nào Các chương trình cũng hết sức đơn giản
Hinh 2.1
II Viết chương trình chơi cờ
1 Biểu diễn bàn cờ và quân cờ
Bàn cờ trong trò chơi cờ Tướng là một bảng chữ nhật bao gồm 9 đường dọc và 10 đường ngang Các quân cờ chia làm hai bên đứng tại các giao điểm của các đường Bàn cờ và vị trí khởi đầu các quân cờ như hình 2.1 Cách đơn giản nhất để biểu diễn một bàn cờ trong máy tính là ta dùng một mảng hai chiều, kích thước 9 x 10:
BYTE piece[10,9];
Mảng trên hoạt động tốt nhưng có cái bất tiện là ta phải dùng tới hai chỉ số để truy cập vào mảng (ví dụ vị trí quân Pháo góc trên bên trái (cột 2, dòng 3) là piece[3, 2]) Một cải tiến nhỏ là ta dùng mảng một chiều như sau:
BYTE piece[90];
Truy nhập đến vị trí quân Pháo góc trên bên trái lúc này là piece[20]
Trang 3Các ô của mảng sẽ chứa những giá trị khác nhau cho biết đó là quân cờ gì Mỗi quân cờ
sẽ được gán một mã số khác nhau như bảng dưới đây Các chỗ trống (không có quân cờ)
sẽ được điền kí hiệu trống (EMPTY):
Quân cờ Kí hiệu Giá trị
Ngoài mục đích gán mỗi quân cờ một mã số để phân biệt, mã này còn giúp ta ước lượng
sơ bộ tầm quan trọng của quân cờ đó
Như vậy, lúc khởi đầu, các ô trong mảng sẽ được gán các giá trị quân cờ nhờ khai báo const (trong Pascal) như dưới, trong đó BOARD_SIZE (kích thước bàn cờ = 90) là một hằng số đã được định nghĩa trước đó:
Trang 4Bên Kí hiệu Giá trị
m_side := m_xside; m_xside := 3 - m_xside;
Trang 5Ngoài ra, ta còn khai báo biến computerside cũng chỉ có một trong hai giá trị LIGHT và DARK nhằm cho biết bên nào là máy
2 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
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.Struct MOVE { short from,dest; };
Trang 6Kiể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)
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
Trang 7đứ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;
cờ mở rộng, mỗi chiều có thêm 2 đường nữa (bàn cờ mới có kích 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)
Trang 8Việ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 chobiế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 sangtrá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ưunướ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
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
Trang 9nghè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
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 }
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
Trang 10đượ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 đó vào danh sách các nước đi
3 Đánh giá một thế cờ
Đá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
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:
Trang 11 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 đốiphươ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)
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 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
Trang 12Trong 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):
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