LỜI MỞ ĐẦU C ờ 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. 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!
Trang 1HỌ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
Trang 2LỜ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:
Trang 3I) 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);
}
}
}
Trang 4Ở đâ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
Trang 5cho 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;
Trang 6}
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;
}
Trang 7else 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)
{
Trang 8if (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;
}
Trang 9if ((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.
Trang 10public 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;
Trang 11//
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)
Trang 12{
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)
{
Trang 13songoi++;
}
}
}
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)