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

Báo cáo đồ án trí tuệ nhân tạo: Mô tả không gian trạng thái trò chơi cờ tướng theo giải thuật minimax

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 (198.32 KB, 14 trang )

HỌC VIỆN KỸ THUẬT QUÂN SỰ
KHOA CÔNG NGHỆ THÔNG TIN
BÁO CÁO MÔN HỌC
TRÍ TUỆ NHÂN TẠO
Giáo viên hướng dẫn: Ngô Hữu Phúc
HÀ NỘI 3/2010
LỜI MỞ ĐẦU
ờ tướng là một trò chơi giải trí mang tính tư duy logic cao. Ván cờ được tiến hành giữa
hai đấu thủ, một người cầm quân đỏ, một người cầm quân đen. Mục đích của mỗi đấu
thủ là tìm mọi cách đi quân trên bàn cờ đúng luật để chiếu tướng của đối phương và
giành thắng lợi.
C
Bàn cờ là một hình chữ nhật do 9 đường dọc và 10 đường ngang cắt vuông góc tại 90 điểm hợp
thành. Một khoảng trống gọi là sông nằm giữa bàn cờ, chia bàn cờ thành hai phần đối xứng
bằng nhau. Mỗi ván cờ mở đầu phải có đủ 32 quân, gồm 7 loại chia đều cho mỗi bên gồm 16
quân đỏ và 16 quân đen.
Ở đây em thực hiện mô tả không gian trạng thái trò chơi cờ tướng theo giải thuật minimax.Mặc
dù em đã cố gắng mô tả các luật đi của các quân cờ, xong vẫn không thể tránh khỏi các thiếu
sót. Rất mong sự giúp đỡ tận tình của Thầy để chương trình của em có thể được hoàn thiện.
Em xin chân thành cảm ơn Thầy!
Chương trình:
I) Giao diện:
II). Các hàm sử dụng trong chương trình:
Mỗi 1 quân cờ ta đều phải lưu tọa độ, tên và màu sắc của quân cờ. Ở đây em sử dụng mảng Bàn
Cờ để lưu mỗi quân cờ với những thuộc tính đó.
Các quân cờ có thể đi đến bất kì điểm nào trên bàn cờ. Muốn di chuyển được quân cờ ta phải xác
định được tọa độ của quân cờ và tọa độ của các vị trí trên bàn cờ. Vì vậy ta phải có 1 hàm để xác
định tọa độ của quân cờ và lưu các tọa độ đó vào mảng Bàn Cờ như sau:
public void XacDinhToaDoDiem()
{
int i, j;


ToaDoDiem td;
for (i = 0; i < 10; i++)
{
for (j = 0; j < 9; j++)
{
td = new ToaDoDiem(40 + j * D, 40 + i * D1);
ArrayToaDoDiem.Add(td);
}
}
}
Ở đây em quy định D là độ rộng của ô theo chiều ngang, D1 là độ rộng các ô theo chiều dọc.
td là 1 đối tượng thuộc class ToaDoiDiem. Duyệt từng tọa độ của các điểm trên bàn cờ và add
nó vào mảng ToaDoDiem. Vậy là tọa độ của các điểm đã được lưu.
Tiếp theo, để kiểm tra việc quân cờ click vào đúng 1 điểm nào đó xác định trên bàn cờ hay
không em xây dựng hàm kiểm tra như sau:
public ToaDoDiem KiemTra(int x, int y)
{
ToaDoDiem tdChon = null;
int dX, dY;
double khoangcach;
foreach (ToaDoDiem td in ArrayToaDoDiem)
{
dX = Math.Abs(x - td.x);
dY = Math.Abs(y - td.y);
khoangcach = Math.Sqrt(Math.Pow(dX, 2) + Math.Pow(dY, 2));
if (khoangcach <= 30)
{
tdChon = td;
break;
}

}
return tdChon;
}
Quân cờ được quy định ban đầu có bán kính bằng 30 cm. Vì vậy khi kích vào bất kì điểm nào
trên bàn cờ, hàm KiemTra sẽ tính toán khoảng cách giữa tọa độ của chuột khi đó và tọa độ của
điểm gần đó nhất. Nếu khoảng cách giữa điểm đó với tọa độ của chuột nhỏ hơn hoặc bằng bán
kính của quân cờ thì di chuyển quân cờ ra vị trí đó.
Khi ăn quân cờ, bắt buộc ta phải xóa bỏ quân cờ vừa bị ăn, ở đây em xây dựng hàm xóa quân cờ.
Nó sẽ xóa bỏ tên, màu sắc và tọa độ của quân cờ đó ra khỏi màn hình. Hàm xóa được viết như
sau:
public void XoaQuanCo(QuanCo q)
{
foreach (QuanCo qc in BanCo)
{
if (qc.x == q.x && qc.y == q.y)
{
BanCo.Remove(qc);
break;
}
}
}
Hàm này thực hiện duyệt từng quân cờ trong mảng bàn cờ và xóa quân cờ đó ra khỏi bàn cờ.
Việc di chuyển quân cờ đã thực hiện xong, nhưng các quân cờ ở đây có thể di chuyển lung tung
trên bàn cờ. Nó vẫn chưa tuân theo đúng luật của cờ tướng. Muốn xây dựng các nước đi đúng
cho quân cờ tuân thủ luật cờ tướng Việt Nam hiện hành, ta phải viết các hàm dành riêng cho mỗi
quân cờ như sau:
Ta nhận thấy quân Tốt có nước đi đơn giản nhất, nó chỉ di chuyển lên trên (đối với quân đen) và
di chuyển xuống dưới (đối với quân đỏ), khi sang sông nó mới được phép đi ngang mà mỗi nước
đi chỉ di chuyển được 1 ô. Hàm cho quân Tốt đi như sau:
public bool TotDi(QuanCo qTot, ToaDoDiem tdDich)

{
if (qTot.isRed)
//tốt đỏ
{
if (tdDich.y > qTot.y)
//xác định là đi xuống
{
if ((tdDich.y - qTot.y) == D1)
{
return true;
}
else return false;
}
else if (tdDich.y == qTot.y)
//đi ngang
{
if ((qTot.y >= (5 * D1 + 40)) && (Math.Abs(tdDich.x -
qTot.x) == D))
{
return true;
}
else return false;
}
else return false;
}
else
//tốt đen
{
if (tdDich.y < qTot.y)
{

if ((qTot.y - tdDich.y) == D1)
{
return true;
}
else
return false;
}
else if (qTot.y == tdDich.y)
{
//tot den di ngang
if ((qTot.y <= (4 * D1 + 40)) && (Math.Abs(tdDich.x -
qTot.x) == D))
{
return true;
}
else
return false;
}
else return false;
}
}
Đầu tiên ta xét với quân đỏ. So sánh tọa độ y của đích đến có lớn hơn tọa độ y của quân tốt hay
không (vì lúc này Tốt vẫn chưa sang sông nên chỉ có thể đi dọc, vì vậy ta chỉ cần quan tâm tới
tung độ của nó). Nếu tung độ của đích lớn hơn tung độ của quân tốt chứng tỏ là Tốt đi xuống
dưới. Tiếp tục ta xét tung độ của điểm đích có lớn hơn tung độ của con Tốt đúng bằng D1 hay
không? Nếu đúng thì chứng tỏ nó đang đi dọc xuống 1 bước
Nếu tọa độ y của đích bằng tọa độ y của quân tốt chứng tỏ lúc này tốt đang đi ngang. Ta xét xem
hoành độ của đích có lớn hơn hoành độ của quân tốt đúng bằng khoảng cách D hay không. Nếu
đúng thì chứng tỏ Tốt đi sang ngang đúng 1 nước. Hàm trả về true, ngược lại hàm trả về false.
Đối với Tốt đen cũng tương tự chỉ khác là quân tốt chỉ được phép đi lên trên và khi nào sang

sông nó mới được đi sang ngang.
Ta nhận thấy quân Xe và quân Pháo có nước đi giống nhau. Vì vậy ta viết hàm XePhaoDi chung
cho 2 quân này. Hàm này như sau:
public bool XePhaoDi(QuanCo qXe, ToaDoDiem tdDich)
{
if (qXe.x == tdDich.x)
//đi dọc
{
int step = (tdDich.y - qXe.y) / D1;
int i;
int x, y;
if (step == 1 || step == -1)
{
return true;
}
else
{
if (step >= 2)
//xe đi xuống
{
for (i = 1; i < step; i++)
{
x = qXe.x;
y = qXe.y + i * D1;
foreach (QuanCo qc in BanCo)
{
if (qc.x == x && qc.y == y)
{
return false;
}

}
}
return true;
}
else if (step <= -2)
//xe đi lên
{
for (i = -1; i > step; i )
{
x = qXe.x;
y = qXe.y + i * D1;
foreach (QuanCo qc in BanCo)
{
if (qc.x == x && qc.y == y)
{
return false;
}
}
}
return true;
}
return false;
}
}
else if (qXe.y == tdDich.y)
//đi ngang
{
int step = (tdDich.x - qXe.x) / D;
int i, x, y;
if (step == 1 || step == -1)

{
return true;
}
else
{
if (step >= 2)
//di sang phai
{
for (i = 1; i < step; i++)
{
x = qXe.x + i * D;
y = qXe.y;
foreach (QuanCo qc in BanCo)
{
if (qc.x == x && qc.y == y)
return false;
}
}
return true;
}
else if (step <= -2)
//di sang trai
{
for (i = -1; i > step; i )
{
x = qXe.x + i * D;
y = qXe.y;
foreach (QuanCo qc in BanCo)
{
if (qc.x == x && qc.y == y)

return false;
}
}
return true;
}
return false;
}
}
return false;
}
Ta xét quân đi theo chiều dọc, với biết step là số bước dịch chuyển. Nếu số bước dịch chuyển là
1 hoặc -1 (xuống hoặc đi lên) thì hàm trả về true (đi 1 bước luôn đúng). Nếu số bước dịch
chuyển lớn hơn 2 hoặc nhỏ hơn -2 thì ta phải xét xem giữa bước dịch chuyển đến điểm đích có
quân cờ nào không? Nếu có thì không thể đi được. Hàm trả về false.
Tương tự đối với đi xuống, đi sang trái hoặc đi sang phải. Nếu có quân cờ giữa điểm cần di
chuyển và điểm đến thì không thể đi được. lúc này trên màn hình sẽ xuất hiện thông báo:
Xét tiếp hàm Sĩ đi. Quân này chỉ có thể đi xung quanh để bảo vệ tướng nhưng đi theo đường
chéo và mỗi lần đi chỉ đi được 1 bước. Vì vậy độ dịch chuyển theo hoành độ phải bằng D và theo
tung độ phải bằng D1. Ở đây ta xét đối với từng trường hợp của quân sĩ bên quân đỏ sau đó ta
tịnh tiến vùng này sang quân đen bằng cách cộng thêm 1 khoảng cách distance = 7 ô. Hàm này
như sau:
public bool SiDi(QuanCo qSi, ToaDoDiem tdDich)
{
int distance = 0;
if (!qSi.isRed)
{ distance = 7 * D1; }
if ((qSi.x == 40 + 3 * D) && (qSi.y == 40 + distance))
{
if ((tdDich.x == 40 + 4 * D) && (tdDich.y == 40 + D1 +
distance))

{
return true;
}
else return false;
}
if ((qSi.x == 40 + 5 * D) && (qSi.y == 40 + distance))
{
if ((tdDich.x == 40 + 4 * D) && (tdDich.y == 40 + D1 +
distance))
{
return true;
}
else return false;
}
if ((qSi.x == 40 + 3 * D) && (qSi.y == 40 + 2 * D1 + distance))
{
if ((tdDich.x == 40 + 4 * D) && (tdDich.y == 40 + D1 +
distance))
{
return true;
}
else return false;
}
if ((qSi.x == 40 + 5 * D) && (qSi.y == 40 + 2 * D1 + distance))
{
if ((tdDich.x == 40 + 4 * D) && (tdDich.y == 40 + D1 +
distance))
{
return true;
}

else return false;
}
if ((qSi.x == 40 + 4 * D) && (qSi.y == 40 + D1 + distance))
{
if ((tdDich.x == 40 + 3 * D) && (tdDich.y == 40 + distance))
{
return true;
}
if ((tdDich.x == 40 + 5 * D) && (tdDich.y == 40 + distance))
{
return true;
}
if ((tdDich.x == 40 + 3 * D) && (tdDich.y == 40 + 2 * D1 +
distance))
{
return true;
}
if ((tdDich.x == 40 + 5 * D) && (tdDich.y == 40 + 2 * D1 +
distance))
{
return true;
}
return false;
}
return false;
}
Hàm tướng đi, ta đã biết quân tướng chỉ đi trong vùng ô vuông gạch chéo cùng với phần mà Sĩ
được đi nhưng Tướng khác Sĩ ở chỗ là nó đi theo đường dọc, ngang chứ không đi chéo như Sĩ.
Mỗi lần nó dịch chuyển chỉ được 1 bước.
public bool TuongDi(QuanCo qTuong, ToaDoDiem tdDich)

{
int distance = 0;
if (!qTuong.isRed)
{ distance = 7 * D1; }
//
int dX, dY;
dX = Math.Abs(qTuong.x - tdDich.x);
dY = Math.Abs(qTuong.y - tdDich.y);
if ((dX == D) && (dY == D1))
{
return false;
}
else
{
if ((dX == D) || (dY == D1))
{
if ((tdDich.x >= 40 + 3 * D) && (tdDich.x <= 40 + 5 * D)
&& (tdDich.y >= 40 + distance) && (tdDich.y <= 40 + 2 * D1 + distance))
{
return true;
}
}
}
return false;
}
Cũng tương tự như hàm sĩ, ta xét quân tướng của phe đỏ sau đó ta tịnh tiến tọa độ theo trục y để
xét tiếp với quân tướng của phe đen bằng cách cộng vào tọa độ y của chúng 1 khoảng distance =
7*D1.
Xét hàm Tượng đi. Để tránh nhầm lẫn biến qTướng và qTượng ta đặt biến viết với hàm Tượng đi
là VoiDi. Mỗi nước đi của Tượng đi chéo hai bước tại trận địa bên mình và không được qua

sông. Nếu ở giữa đường chéo có quân khác đứng thì quân Tượng bị cản, không đi được. Ta đặt
dX và dY là hoành độ và tung độ dịch chuyển của quân Tượng nó phải thỏa mãn là dịch chuyển
2 bước (tức dX = 2*D và dY = 2*D1, xGiữa và yGiữa là tọa độ để ta kiểm tra xem giữa đường
chéo có quân cờ nào đứng không? Nếu có thì hàm trả về false. Nếu không có thì ta tiếp tục xét
tiếp. Hàm tượng đi như sau:
public bool VoiDi(QuanCo qVoi, ToaDoDiem tdDich)
{
int dX, dY;
int xGiua, yGiua;
dX = Math.Abs(qVoi.x - tdDich.x);
dY = Math.Abs(qVoi.y - tdDich.y);
if ((dX == 2 * D) && (dY == 2 * D1))
{
//tinh x giua va y giua;
if (tdDich.x > qVoi.x)
xGiua = qVoi.x + D;
else xGiua = qVoi.x - D;
if (tdDich.y > qVoi.y)
yGiua = qVoi.y + D1;
else yGiua = qVoi.y - D1;
//
bool tontai = true;
foreach (QuanCo qc in BanCo)
{
if (qc.x == xGiua && qc.y == yGiua)
{
tontai = false;
break;
}
}

if (tontai)
{
if ((yGiua == 40 + 4 * D1) || (yGiua == 40 + 5 * D1))
{
return false;
}
else return true;
}
else return false;
}
else
{
return false;
}
}
Giờ ta xét hàm Mã đi:
public bool MaDi(QuanCo qMa, ToaDoDiem tdDich)
{
int dX, dY;
int xKt = 0, yKt = 0;
dX = Math.Abs(qMa.x - tdDich.x);
dY = Math.Abs(qMa.y - tdDich.y);
bool th1, th2;
if (dX == D && dY == 2 * D1)
{
th1 = true;
}
else th1 = false;
if (dX == 2 * D && dY == D1)
{

th2 = true;
}
else th2 = false;
//
if (th1 || th2)
{
if (th1)
{
xKt = qMa.x;
if (tdDich.y > qMa.y)
yKt = qMa.y + D1;
else
yKt = qMa.y - D1;
}
else if (th2)
{
yKt = qMa.y;
if (tdDich.x > qMa.x)
xKt = qMa.x + D;
else
xKt = qMa.x - D;
}
//co xkt va ykt
foreach (QuanCo qc in BanCo)
{
if (qc.x == xKt && qc.y == yKt)
{
return false;
break;
}

}
return true;
}
else return false;
}
Hàm Mã đi có thể nói là hàm rắc rối nhất trong các nước đi của các quân. Nó đi theo đường chéo
hình chữ nhật của hai ô vuông liền nhau. Nếu ở giao điểm liền kề bước thẳng dọc ngang có một
quân khác đứng thì Mã bị cản, không đi được. Ở đây em dùng biến xKt và yKt để kiểm tra xem
giữa giao điểm liền kề bước di chuyển dọc ngang có quân cờ nào ở đó hay không. Nếu có thì
hàm trà về false.
Xét hàm pháo ăn:
public bool PhaoAn(QuanCo qPhaoAn, ToaDoDiem tdDich)
{
int songoi = 0;
if (qPhaoAn.x == tdDich.x)
//đi dọc
{
int step = (tdDich.y - qPhaoAn.y) / D1;
int i;
int x, y;
if (step == 1 || step == -1)
{
return false;
}
else
{
if (step >= 2)
//pháo đi xuống
{
for (i = 1; i < step; i++)

{
x = qPhaoAn.x;
y = qPhaoAn.y + i * D1;
foreach (QuanCo qc in BanCo)
{
if (qc.x == x && qc.y == y)
{
songoi++;
}
}
}
if (songoi == 1)
return true;
else return false;
}
else if (step <= -2)
//pháo đi lên
{
for (i = -1; i > step; i )
{
x = qPhaoAn.x;
y = qPhaoAn.y + i * D1;
foreach (QuanCo qc in BanCo)
{
if (qc.x == x && qc.y == y)
{
songoi++;
}
}
}

if (songoi == 1)
return true;
else return false;
return true;
}
return false;
}
}
else if (qPhaoAn.y == tdDich.y)
//đi ngang
{
int step = (tdDich.x - qPhaoAn.x) / D;
int i, x, y;
if (step == 1 || step == -1)
{
return false;
}
else
{
if (step >= 2)
//di sang phai
{
for (i = 1; i < step; i++)
{
x = qPhaoAn.x + i * D;
y = qPhaoAn.y;
foreach (QuanCo qc in BanCo)
{
if (qc.x == x && qc.y == y)
songoi++;

}
}
if (songoi == 1)
return true;
else return false;
}
else if (step <= -2)
//di sang trai
{
for (i = -1; i > step; i )
{
x = qPhaoAn.x + i * D;
y = qPhaoAn.y;
foreach (QuanCo qc in BanCo)
{
if (qc.x == x && qc.y == y)
songoi++;
}
}
if (songoi == 1)
return true;
else return false;
return true;
}
return false;
}
Pháo chỉ ăn được khi có quân đứng trước nó làm ngòi cho nó bất kể là quân cùng phe hay khác
phe. Ta đặt 1 biến songoi để tính số ngòi của pháo. Pháo này chỉ ăn được khisố ngòi giữa quân
pháo và quân định ăn là 1. Các trường hợp khác hàm đều trả về false. Số ngòi được tính khi trên
đường đi của nó tới quân định ăn nếu gặp bất kì quân nào thì số ngòi được cộng thêm 1. Đặt biến

step là biến tính số bước dịch chuyển. Nếu số bước này bằng 1 hoặc -1 thì hàm trả về false (vì
nếu chỉ dịch chuyển 1 bước thì Pháo không thể ăn được).

×