Chƣơng trình chơi cờ ứng dụng trí tuệ nhân tạo

Một phần của tài liệu HỌ VI ĐIỀU KHIỂN PIC VÀ VI ĐIỀU KHIỂN PIC18F67J60 (Trang 104)

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.

Ta dùng một mảng piece có độ lớn 90 để biểu diễn toàn bộ bàn cờ.

Hình 7.4: Biểu diễn bàn cờ bằng số, vị trí quân pháo ứng với vị trí 20

Cá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

Quân cờ Giá trị Tốt (Chốt) 0 Sĩ 1 Tƣợng 2 Mã 3 Pháo 4 Xe 5

Tƣớng 6

Trống 7

Ngoài ra ta dùng một mảng khác gọi là mảng color để biểu diễn màu của quân

Bên Kí hiệu Giá trị Xanh LIGHT 0

Đỏ DARK 1

Trống EMPTY 7

Để biết bên nào tới lƣợt đi, ta dùng một biến toàn cục side chứa một trong hai giá trị LIGHT và DARK. Một biến toàn cục khác xside sẽ có sẵn giá trị ngƣợc với side để tiện tính toán (ví dụ nếu side = LIGHT thì xside = DARK). Khi tới phiên đối phƣơng đi, ta cần đảo ngƣợc giá trị trong cả side và xside

Trên giao diện đồ họa, ta sử dụng 90 bức hình ứng với 90 vị trí. Nhƣ vậy, để biểu diễn toàn bộ bàn cờ, ta lần lƣợt quét 2 mảng trên nhƣ sau:

private void DrawAllPieces() {

for (Byte i = 0; i < 90; i++)

{

Drawpieces( piece[i],color[i],i) //

} }

7.4.2 Sinh nƣớc đi (Hàm GEN)

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ó.

 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.

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.

public struct MOVE

{

public SByte from;

public SByte dest;

}

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).

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ờ đó.

Kỹ thuật MAILBOX: 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 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 7.5: Chuyển tƣơng ứng bàn cờ từ bảng 9x10 sang bảng 13x14 để kiểm tra giới hạn ngoài

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.

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.

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.

Hình 7.6: Mặt nạ kiểm tra một quân cờ có đƣợc phép đến vị trí nào đó không? 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;j++;j<=8 ) // Số hướng đi tối đa

if (offset[p,j] = 0) break;

x=mailbox90[i]; // Chuyển sang bàn cờ mở

rộng

if (p in [ROOK, CANNON]) n := 9 else n := 1;

for(t=1;t++,t<=n) // Số nước có thể đi theo một

hướng

{

if((p=PAWN) & (side=LIGHT)) x = x - offset[p, j]

else x = x + offset[p, j]; // Nước đi mới

y = mailbox182[x]; // Chuyển về toạ độ

if side = DARK t = y

else t = 90-y;

if ((y=-1) | // Ra ngoài lề ?

((legalmove[t] & maskpiece[p])=0))

// Được phép ở vị trí này không ?

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 đƣợ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.

7.4.3 Đánh giá một thế cờ (Hàm EVAL)

Đá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à hay 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. Đ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.

Trong giới hạn của luân văn, ta áp dụng 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.

Đ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 trung bình từng quân với tỉ lệ mà mọi ngƣời thƣờng chấp nhận:

Tốt 10 (20 nếu đã qua sông) Sĩ 20 Tƣợng 20 Mã 40 Pháo 45 Xe 90

Ngoài ra các điểm số này đƣợc cụ thể hóa bằng các mảng 90 vị trí trên bàn cờ. Ứng với mỗi vị trí sẽ có số điểm tƣơng ứng. Ví dụ ta có bảng điểm của quân tốt nhƣ sau:

static Byte[, ,] pointtable =

{{ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 12, 0, 15, 0, 12, 0, 10, 10, 0, 13, 0, 10, 0, 13, 0, 10, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 22, 22, 22, 21, 21, 20, 20, 21, 21, 23, 23, 23, 21, 21, 20, 20, 21, 21, 23, 22, 23, 21, 21, 20, 11, 12, 13, 14, 14, 14, 13, 12, 11}, Hàm tính điểm của bàn cờ nhƣ sau:

public static int Eval() {

int i, s = 0;

for (i = 0; i < BOARD_SIZE; i++)

{ if (color[i] == DARK) s += pointtable[piece[i],

DARK, i];

else if (color[i] == LIGHT) s -=

pointtable[piece[i], LIGHT, i]; }

if (side == LIGHT) s = -s;

}

Trong đó bonus() là hàm tính các giá trị điểm thƣởng cho thế cờ.

7.4.4 Xử lý một nƣớc đi thử

Trong quá trình tính toán tìm nƣớc đi tốt nhất cho mình, máy thƣờng phải đi "thử" một nƣớc rồi lại tính tiếp. Sau đó nó sẽ phục hồi lại nƣớc đi này. Do quá trình thử - phục hồi diễn ra rất nhanh và không hiện ra màn hình nên máy có thể thử hàng chục triệu lần mỗi khi đến lƣợt. Việc đi thử đƣợc thực hiện nhờ gọi hàm Makemove. Việc khôi phục nƣớc đi này nhờ gọi thủ tục UnMakemove. Nhƣ vậy, chƣơng trình thƣờng xuyên thực hiện các câu lệnh dạng nhƣ sau:

while {tất cả các nước đi có thể có từ một thế cờ} do

{

Makemove(một_nước_đi); //Đi thử một nước best = Tính điểm của nước đi thử đó;

UnMakemove; //phục hồi nước đi này

Lưu nước đi có điểm cao nhất; }

7.4.5 Thuật toán tìm kiếm AlphaBeta

Thuật toán AlphaBeta dựa trên nguyên lý đã phân tích ở chƣơng 4 có dạng sau:

public static short AlphaBeta(short alpha, short beta, short

depth) {

short i, value, best;

if (depth == 0) return Eval();

Gen();

best = -INFINITY;

for (i = m_gen_begin[m_ply];(i<m_gen_end[m_ply])&&(best<

beta); i++) //vòng lặp tất cả các nước sinh ra {

if (best > alpha) alpha = best;

if (MakeMove(m_gen_dat[i].m)) value = 1000 - m_ply;

UnMakeMove();

if (value > best)

{

best = value;

if (m_ply == 0) m_newmove = m_gen_dat[i].m;

//lưu nước đi tốt nhất ở độ sâu 0 }

return best; }

}

Nƣớc đi tốt nhất (điểm cao nhất) ở độ sâu 0 (ply = 0) đƣợc lƣu vào một biến toàn cục newmove. Máy sẽ chọn đi theo nƣớc này.

Thủ tục gọi hàm AlphaBeta chỉ có các lệnh đơn giản nhƣ dƣới. Nó gọi hàm này với các giá trị khởi đầu thích hợp để bắt đầu tìm kiếm.

public static void ComputerThink() {

PreCalculate();

if (openningbookcs.FollowOpeningBook())

return;//kiểm tra dữ liệu khai cuộc, sẽ trình

bày ở mục sau

InitGen(); SetMaterial();

m_newmove.from = NOMOVE;

best = AlphaBeta(-INFINITY, INFINITY, MAX_PLY) }

7.4.6 Cập nhật một nƣớc đi

Sau khi "suy nghĩ" xong, đã đến lúc máy hoặc ngƣời phải thực hiện một nƣớc đi thực sự. Chƣơng trình phải cập nhật tất cả các thông tin cần thiết liên quan đến nƣớc đi và hiện hình những thay đổi. Việc này đƣợc thực hiện nhờ gọi hàm UpdateNewMove. Nó có khá nhiều chỗ giống nhƣ hàm Makemove nhƣng đơn giản hơn. Tham số quan trọng nhất mà nó quan tâm là nƣớc đi đƣợc ngƣời hoặc máy chọn đi (đặt trong biến toàn cục newmove). Hàm

cũng đồng thời sẽ kiểm tra tình trạng thắng cờ của bên vừa đi (ăn đƣợc Tƣớng đối phƣơng) và trả về giá trị true nếu có một bên thắng. Vòng lặp chính sẽ căn cứ vào giá trị chân lí này để ngừng chơi.

7.4.7 Xây dựng dữ liệu khai cuộc

Biểu diễn nƣớc cờ bằng ngôn ngữ máy:

- Đánh số từ 0 tới 9 ứng với 10 hàng ngang - chữ từ A tới I ứng với 9 cột.

Dữ liệu khai cuộc là một file txt chứa giá trị các nƣớc đi đầu tiên của một ván cờ( trung bình khoảng 20 nƣớc đầu tiên). Mỗi 1 ván cờ là một dòng.

Ví dụ 1 ván cờ có thể đƣợc biểu diễn nhƣ sau:

H2E2 B9C7 C3C4 H7F7 B0C2 H9G7 C2D4 D9E8 B2C2 C9E7 …

Nhƣ vậy, mỗi khi bắt đầu game, ta có mảng chứa các giá trị các nƣớc đi mẫu.

Mỗi khi tới lƣợt máy, máy sẽ kiểm tra xem các nƣớc đi trƣớc phù hợp với các chiến thuật khai cuộc nào. Nếu tìm thấy nƣớc đi giống với chiến thuật này, máy sẽ chọn và làm theo.

CHƢƠNG 8: KẾT QUẢ ĐẠT ĐƢỢC VÀ HƢỚNG PHÁT TRIỂN ĐỀ TÀI 8.1 Hình ảnh và kết quả đạt đƣợc

8.1.1 Một số hình ảnh của mô hình

Hình 8.1: Module Camera

Hình 8.3: Phần cánh tay Robot và bàn cờ

8.1.2 Kết quả đạt đƣợc

 Cánh tay Robot di chuyển quân cờ khá chính xác trong thời gian cho phép.

 Xử lý ảnh: Chƣơng trình nhận ra nƣớc đi của ngƣời chơi với độ chính xác gần nhƣ tuyệt đối. Thời gian xử lý nhanh, khoảng 800ms (Phụ thuộc tốc độ CPU).

 Chƣơng trình chơi cờ:

o Với độ sâu 4: số nút tính đƣợc sau khi đi nƣớc đầu tiên là 150480 nút,hệ số phân nhánh là 41 với thời gian đo đƣợc xấp xỉ 100ms, thời gian suy nghĩ không đáng kể (Phụ thuộc tốc độ CPU).

o Với độ sâu 5: số nút tính đƣợc là 5574794, tức gấp 37 lần độ sâu 4. Thời gian suy nghĩ lúc này lên đến gần 5 giây (Phụ thuộc tốc độ CPU).

o Với độ sâu 6: số nút tăng rất lớn xấp xỉ 63 triệu nút với thời gian gần 50 giây.

8.2 Hạn chế của đề tài và hƣớng phát triển 8.2.1 Những khó khăn gặp phải 8.2.1 Những khó khăn gặp phải

 Do xử dụng động cơ bƣớc có công suất không phù hợp với thiết kế của cánh tay nên lúc di chuyển có thể bị trƣợt bƣớc, gây thiếu chính xác.

 Camera dùng trong xử lý ảnh cần cố định đƣợc vị trí tƣơng đối so với bàn cờ nên chƣơng trình dễ bị sai lệch nếu quá trình di chuyển mô hình không cẩn thận làm vƣớng vào module này.

8.2.2 Biện pháp khắc phục

 Để khắc phục hiện tƣợng trƣợt bƣớc cần di giảm vận tốc các động cơ bƣớc để tăng

Một phần của tài liệu HỌ VI ĐIỀU KHIỂN PIC VÀ VI ĐIỀU KHIỂN PIC18F67J60 (Trang 104)