thiết kế trò chơi cờ ca-rô
Trang 1Mục lục
Cấu hình hệ thống chi tiết 44 IV.2.5.Chức năng nút Help _53
IV.2.6.Chức năng nút About _54
IV.2.7.Chức năng nút Save & Exit 55
Trang 2THIẾT KẾ TRÒ CHƠI CỜ CA-RO
I Đặc tả hệ thống
I.1 Giới thiệu trò chơi cờ CA-RO
Cờ CA-RO ( hay còn có tên khác là GO-MO-KU) là trò chơi nằm trong thể loại các trò chơi đối kháng ( cờ vua, cờ tường, cờ vây ), diễn ra giữa 2 đấu thủ Đặc điểm của dạng trò chơi này như sau:
- Có 2 đấu thủ, mỗi người chỉ đi 1 nước khi tới lượt
- Trận đấu không kéo dài vô tận, phải diễn ra hòa, hoặc một bên thắng và bên kia thua Thông thường ta hay gọi các trò chơi này là loại cờ Đôi khi ta gọi đây là các trò chơi Minimax Hình 1.1
là ví dụ về 1 số trò chơi nằm trong thể loại các trò chơi đối kháng:
Trang 3(Hình 1.1)
I.2 Lịch sử trò chơi cờ CA-RO
Cờ caro chính là môn cờ logic lâu đời và cổ xưa nhất trên Trái Đất
Cờ caro đã được sáng tạo từ nhiều nền văn minh khác nhau một cách độc lập Nó bắt đầu xuất hiện từ năm 2000 trước CN ở sông Hoàng Hà, Trung Quốc Một số nhà khoa học đã tìm thấy bằng chứng chứng minh Caro đã được phát minh ở Hy lạp cổ đại và ở Châu Mỹ trước thời Colombo
Môn cờ cổ của Trung Quốc là Wutzu Cờ Caro du nhập từ Trung Quốc vào Nhật Bản từ khoảng năm 270 trước CN Nó thường được gọi là Gomoku nhưng cũng có các tên gọi khác tuỳ theo thời gian
và địa phương như Kakugo, gomoku-narabe, Itsutsu-ishi Người
ta đã tìm thấy một trò chơi cổ từ một di tích ở Nhật năm 100 sau
CN và thấy nó là một biến thể của Caro Nó đã lan truyền nhanh chóng với cái tên Kakugo (trò 5 quân) Các nhà sử học nói rằng
Trang 4vào các thế kỷ 17 và 18, mọi người đều chơi trò này-người già cũng như người trẻ Năm 1858, khi quyển sách đầu tiên về trò chơi này được xuất bản, nó được gọi là Kakugo Nó tiếp tục được chơi, được gọi với nhiều tên khác nhau như Goren, Goseki, rồi Gomokunarabe, Gomoku và phát triển cho đến ngày nay thành thể loại phức tạp nhất trong họ hàng đông đúc của nó, là Renju (chuỗi ngọc trai).
Ta có thể biết mọi thông tin về Renju và các biến thể ở
www.renju.nu
Khi trình độ các kỳ thủ Gomoku được nâng cao, họ nhận ra rằng nếu chỉ chơi đơn giản như trong Gomoku thì đó sẽ là một lợi thế quá lớn cho bên tiên tức bên Đen (thực tế chính là ưu thế thắng) Sau đó một số nhà toán học đã chứng minh được rằng nếu chơi với luật Gomoku trên bàn cờ bằng hoặc rộng hơn 15x15 thì Đen chắc chắn thắng (sure win), và sau đó cách đi cụ thể cũng đã được tìm ra, hệ thống và phân loại (theo tôi biết thì cách thắng hoàn toàn không duy nhất như nhiều người chờ đợi, mà thực tế có nhiều nhánh thắng (win branch) cho Đen)
Để dễ hình dung rõ hơn tôi xin được nêu lại luật của Gomoku cổ :-Bàn cờ 15x15
-Đen đi trước
-Ai tạo được nước năm (1 hàng 5 quân liền nhau) thì thắng
-Các nước Overline (>= 6 quân) không có giá trị với cả hai bên và không bị coi là lỗi (cấm)
Từ đó, Gomoku lâm vào một giai đoạn khủng hoảng Khả năng đánh thắng 100 phần trăm của Đen đã làm trò chơi này mất đi ý nghĩa của nó Có nhiều cải tiến được đề xuất, một số đã bị bỏ qua nhanh chóng, số khác làm xuất hiện các biến thể mới của
Gomoku Ý tưởng chung của các cải tiến là đề ra một số hạn chế
Trang 5cho Đen, nhằm cân bằng ưu thế đi tiên Dưới đây là một số biến thể phổ biến.
Gomoku Hiện nay được chơi chính thức với bàn 13x13 Không có hoà Nếu hết đất thì Trắng thắng Chưa tìm được chứng minh nào cho thấy Đen chắc chắn thắng Tuy nhiên Đen vẫn có ưu thế rất lớn Bạn có thể chơi ở www.itsyourturn.com
ProGomoku Chơi trên bàn 15x15 Nước đầu của Đen đặt sẵn ở trung tâm Nước thứ ba (nước thứ hai của Đen) phải đặt ngoài hình vuông cấm Hình vuông cấm là hình vuông trung tâm kích thước 5x5 Không có hạn chế cho Trắng Đã có chứng minh Đen chắc chắn thắng trong biến thể này
Pente Biến thể này không còn giống Gomoku Luật bổ sung là có thể ăn quân đối phương Nước ăn quân được thực hiện bằng cách chặn hai đầu một nước hai quân đối phương và ăn hai quân đó Ai tạo được nước năm hoặc ăn được 5 cặp quân trước thì thắng Rất phổ biến ở Mỹ Chơi trên bàn 19x19
Tuy nhiên trò chơi này chỉ thực sự lấy lại được sự hấp dẫn của nó khi phát triển thành thể loại hoàn thiện nhất được chơi ngày nay - Renju Renju cũng đã mất hàng chục năm để thử nghiệm và phát triển những luật mới Cụ thể là vào năm 1899, cái tên Renju
(chuỗi ngọc trai) ra đời và đến năm 1966 thì nó đã được hoàn thiện khi Liên đoàn Renju Nhật Bản - Nihon Renju Sha ra đời và công bố luật Renju chính thức
II Đưa CA-RO vào trong Game PC
II.1 Mục đích, Đối tượng sử dụng và Yêu cầu tối thiểu cho trò chơi
a Mục đích sử dụng
Trang 6Tro chơi được thiết kế nhằm mục đích tăng khả năng suy luận, phán đoán cho người chơi kết hợp với giải trí lành mạnh.
b Đối tượng sử dụng
Trò chơi CA-RO được thiết kế và phát triển với mục tiêu cung cấp miễn phí, nhằm phục vụ cho tất cả các đối tượng yêu thích môn cờ CA-RO, từ học sinh, sinh viên, đến những người lớn tuổi
c Yêu cầu tối thiểu
- Giao diện thân thiện, dễ sử dụng
- Cho phép người chơi có thể chọn nhiều mức độ từ dễ đến khó
Trang 7b Sơ đồ phân rã chức năng:
Trang 8c Đặc tả chi tiết chức năng
New game.
Nhóm chức năng:
Chức năng chơi.
Trang 9- Chức năng này cho phép người chơi bắt đầu 1 ván cờ, khi kích hoạt chức năng này thì trên FORM sẽ xuất hiện nhiều lựa chọn trong phần Game mode, sau khi chọn chế độ và mức độ chơi phù hợp với mình, 1 bàn cờ sẽ hiện ra và trận đấu sẽ được bắt đầu, người chơi có thể sử dụng tổ hợp phím nóng Ctrl + N để thực
hiện chức năng này
Game Mode: đây là chức năng cho phép người chơi có thể lựa
chọn những mức độ và chế độ chơi phù hợp vởi bản thân
- Có 2 chế độ chơi được đưa ra đó là:
+ Người đấu với Người
+ Người đấu với Máy
- Hệ thống sẽ có 3 mức độ chơi cho game thủ lựa chọn ( mặc định là Easy):
Save and Exit.
Nhóm chức năng:
Chức năng chơi.
- Save&Exit nằm trong nhóm chức năng chơi, được hiển thị trên
màn hình khi mới đăng nhập vào trò chơi, hoặc khi đang chơi, người chơi nhấn ESC
- Lưu trữ trạng thái hiện hành của trò chơi lên tệp dữ liệu ghi trên đĩa cứng máy tính, trạng thái này sẽ được nạp trở lại trong lần khởi động trò chơi sau đó Thông tin trạng thái cần lưu bao gồm:
Trang 10tên người chơi, trạng thái các quân cờ trên bàn cờ, lượt đi tiếp theo.
- Khi chức năng được kích hoạt, chương trình đưa ra thông báo
chứng thực yêu cầu, nếu người chơi xác nhận đúng, thực hiện
Save&Exit, ngược lại bỏ qua
- Tất cả được lưu vào một file có định dạng sẵn có đuôi là sav,
tên file do người chơi có thể tự đặt hoặc để tên mặc định do
game quy định
- Người chơi có thể sử dụng tổ hợp phím nóng Ctrl + S để thực hiện chức năng này
Load game.
Nhóm chức năng:
Chức năng chơi.
- Load Game gắn liền với Save Game, khi người chơi đã có
file *.sav nghĩa là có ván cờ đã lưu thì có thể dùng chức năng
này để mở lại ván cờ và chơi tiếp
- Load Game có thể sử dụng bất cứ lúc nào kể cả khi người chơi đang chơi một ván cờ, khi Load Game được gọi nó hỏi
người chơi có muốn lưu lại ván cờ đang chơi không Nếu có thì
chức năng Save Game sẽ được gọi và sau đó mới Load
Trang 11gian kết thúc ván đấu càng ngắn thì điểm dành cho người chơi càng cao.
Sẽ có 5 thứ bậc:
+ 5 sao (*****) Mức cao nhất+ 4 sao (****)
Nhiệm vụ chủ yếu của chức năng Update là truy cập vào
Website chính thức phân phối trò chơi, đưa ra danh sách các cập nhập có thể có, bao gồm:
- Cập nhập phiên bản mới
- Cập nhật các mức độ khó hơn cho trò chơi
Trong trường hợp chưa có phiên bản mới mà người chơi kích hoạt chức năng Update, hệ thống sẽ thông báo cho người chơi biết hiện tại chưa ra phiên bản mới và khoảng thời gian phiên bản mới sắp ra
Người dùng chọn những cập nhập mong muốn rồi bấm vào nút Ok, trường hợp không muốn cập nhập có thể bỏ trắng khung chọn hoặc bấm vào nut Cancel
CA-RO helpl
Nhóm chức năng:
Chức năng trợ giúp.
Trang 12Đóng vai trò như bản hướng dẫn chơi trò chơi Đưa ra:
- Phiên bản trò chơi
+ Đây là phiên bản đầu tiên, Ver 1.0
- Thông tin nhà sản xuất, liên hệ
- Đưa ra địa chỉ email, tiếp nhận những thông tin phản hồi từ
người chơi
III.MÔ TẢ THUẬT TOÁN
Như đã trình bày ở trên, chương trình “Trò chơi Caro” có hai chế
độ chơi, đó là “human vs human”(người chơi với người) và “human vs com”(người chơi với máy) Sau đây là các thuật toán liên quan đến hai chế độ chơi này
1 Chế độ “human vs human” (người chơi với người)
Trong chế độ này, các thao tác chơi cờ hoàn toàn phụ thuộc vào người chơi Máy tính chỉ thực hiện chức năng kiểm tra các ô đã được sử dụng qua đó không cho phép người chơi đánh vào ô đã sử dụng đó và chức năng kiểm tra trạng thái ván đấu (thắng, hòa, thua) sau mỗi nước đi
a Kiểm tra trạng thái ô cờ
Trang 13Ô cờ chỉ có hai trạng thái “đã đánh” và “chưa đánh” không phân biệt là người chơi nào đánh xuống Do vậy, công việc kiểm tra trạng thái ô cờ là rất dễ dàng bằng cách sử dụng một mảng hai chiều n x n (trong đó n là kích thước bàn cờ) chứa giá trị Boolean: True or False Mảng được khởi tạo với tất cả các giá trị là True ứng với bàn cờ hoàn toàn trắng Mỗi khi có một quân cờ được đánh xuống, thì giá trị phần tử mảng tại vị trí đó sẽ được gán là False.
b Kiểm tra trạng thái ván đấu
Một ván cờ caro kết thúc với ba trạng thái là thắng, hòa, hoặc thua.Tuy nhiên, việc thắng hay thua chỉ là tương đối vì thắng với người này là thua với người kia Do vậy, có thể chia trạng thái ván đấu ra làm hai trạng thái: kết thúc bình thường và kết thúc hòa
-Kết thúc bình thường có nghĩa là sau mỗi một nước
đi, máy sẽ kiểm tra xem quân cờ vừa đánh xuống có tạo thành 5 nước giống nhau liên tiếp không Nếu có thì ván đấu kết thúc và công bố người chiến thắng Nếu không thì lại tiếp tục ván đấu đến khi đạt được một trong hai trạng thái đã nêu Công việc kiểm tra quân cờ vừa đánh xuống có tạo thành 5 nước giống nhau liên tiếp hay không dựa vào thuật đoán đơn giản sau: Khi quân cờ được đánh xuống tại một vị trí (i,j) bất kỳ, máy tính sẽ kiểm tra bốn hướng có thể: ngang, dọc, chéo trái, chéo phải Đối với mỗi một hướng, máy sẽ kiểm tra bốn quân phía trái và bốn quân phía phải quân cờ vừa đánh xuống
Cứ mỗi khi tìm được một quân cờ giống với quân cờ vừa đánh xuống thì sẽ tăng biến đếm lên một Quá trình kiểm tra kết thúc ngay khi biến đếm đạt giá trị là 5, khi
Trang 14đó hàm kiểm tra trả về giá trị là 1 Còn sau khi quá trình kiểm tra kết thúc mà biến đếm chưa đạt được giá trị 5 thì hàm kiểm tra trả về giá trị 0 Sau đây sẽ là đoạn code minh họa thuật toán (đoạn code được minh họa bằng ngôn ngữ C++ để làm thuật toán trong sáng, rõ ràng)
int checkWin(int row,int col)
Trang 15{
count=0;
for (j=i;j<i+5;j++)
if (board[row-j][col+j]==order) {
//Neu so quan co lien tuc bang 5 thi tra ve 1 = ket thuc
//Neu khong thi tra ve gia tri 0 = chua ket thuc
- Kết thúc hòa có nghĩa là sau một số hữu hạn các nước
đi, các ô cờ trên bàn cờ đã được sử dụng hết nhưng lại không có bên nào tạo được 5 nước liên tiếp Sau khi ô cuối cùng được đánh xuống mà không xác định được bên nào thắng thì sẽ phải công bố ván đấu hòa
2 Chế độ “human vs com” (người chơi với máy)
Trang 16Với chế độ chơi này, máy tính sẽ phải tự thực hiện các nước đi, ngoài việc đảm bảo đúng luật thì còn phải đảm bảo một nguyên tắc nữa là “biết suy nghĩ” Do vậy, mỗi quân cờ do máy đánh xuống sẽ không phải chỉ là một quân cờ hợp lệ mà phải biết tấn công cũng như phòng thủ Để thực hiện điều này, phải sử dụng ba kỹ thuật cơ bản nhất: Sinh nước đi (Generate Move), Lượng giá (Evaluate), Tìm kiếm nước đi (Search Game Tree)
a Kỹ thuật “Sinh nước đi” (Generate Move)
Sinh nước đi (generate moves) là một giai đoạn trong quá trình tìm kiếm nước đi, tức là từ trạng thái hiện tại muốn tìm kiếm một nước đi hợp lệ cho lượt của người chơi hiện tại, khi
đó phải sinh tất cả các nước đi có thể có, sau đó sẽ lựa chọn một nước đi tốt nhất (phải đánh giá nó ) trong số những nước đó Đối với kỹ thuật này còn có các kỹ thuật con: Sinh đầy đủ (full generation), sinh nước đi tăng dần (increasing generation), sinh nước đi giảm dần (decrescent generation), sinh tất cả các nước đi (complete generation), sinh nước đi
có chọn lọc (selective generation)
- Sinh đầy đủ (Full Generation)
+Ý tưởng : Từ trạng thái hiện tại, kiểm tra xem còn bao nhiêu nước đi hợp lệ có thể có thì cất tất cả vào stack
+ Phân tích : Thuật toán minh họa for (i = 0; i < BOARD_SIZE;i++)
if (IsLegalMove(i)) // kiểm tra xem (Board[i] == EMPTY)
if (IsNotInGenMove(i)) // kiểm tra xem đã có trong danh sách chưa
StoreGenMove(i) // cất vào stack
+Nhận xét : Trong các loại trò chơi với bàn cờ game) thì kỹ thuật này chỉ ứng dụng khi việc sinh nước đi từ
Trang 17(board-trạng thái có sẵn là đơn giản, ngoài ra việc đánh giá nước đi này là hay hơn nước đi khác phải làm thật chính xác vì nếu không sẽ khó thấy được nước đi nào là tốt hơn nước kia trong các nước đi đã sinh Tuy nhiên, cờ Caro là trò chơi có độ
phân nhánh rất cao (tức là số cách chọn nước đi ứng với 1 trạng thái có sẵn), không những vậy, trong trò Caro thì rất nhiều nước đi nếu không có quan hệ với những nước có sẵn thì sẽ dư thừa và không được chọn Và một điểm khác so với những board-game khác đó là việc sinh nước đi trong Caro là khá nhanh và rất dễ Do đó có thể nói kỹ thuật này nếu ứng dụng trong cờ caro thì sẽ không đạt được hiểu quả cao
- Sinh nước đi tăng dần (Increasing Generation)
+Ý tưởng : Từ trạng thái ban đầu, sinh 1 vài nước đi "nhạy
cảm" (có triển vọng đưa đến chiến thắng), kiểm tra xem những nước đi này sau một vài độ sâu kế tiếp xem có thật sự
là "cúm" chưa ? Nếu chưa thì tiếp tục sinh những nước đi khác rồi kiểm tra tiếp
+ Phân tích: Thuật toán minh họa
* Search : Generate move-gen (step 0) for (i = 0;i < BOARD_SIZE;i++)
if (IsLegalMove(i))
if (IsHasOutlook(i)) // có triển vọng
if (IsNotInGenMove(i)) StoreGenMove(i)
* Sinh một nước đi(dự trữ) trong stack
* Tăng độ sâu và lặp lại cho đến khi đủ sâu sau đó cắt nhánh (có thể xuất hiện hiện tượng cực đại cục bộ)
* Trở lại bước 0
Trang 18+ Nhận xét :Kỹ thuật này khá hay, nhất là khi muốn tính
sâu vào những nước "tốt" (theo quan điểm của người lập trình) để mong chờ search-algorithm cắt nhánh sớm trước khi tiếp tục sinh thêm những nước phức tạp khác Tuy nhiên, nếu ở một trạng thái nào đó, nước đi tốt không nằm ở trong số những nước đi có triển vọng thì mình vẫn phải tiếp tục sinh và cách cài đặt sẽ trở nên phức tạp Rõ ràng cách này chỉ nên sử dụng để đối phó trong trường hợp bị giới hạn thời gian, tức là sẽ lựa 1 nước đi tốt nhất trong số đó "để dành", nếu thời gian không đủ để sinh và tìm kiếm tiếp thì mình sẽ sử dụng để đánh Một nhược điểm khác đó là chưa chắc tìm thấy một nước đi tốt nhất như kỹ thuật đầu tiên (vì nhiều khi thuật toán đã lỡ cắt nhánh ở 1 nước đi mà nó tưởng rằng là tốt nhất, hiện tượng này cũng có thể gọi là "cực đại cục bộ")
- Sinh nước đi giảm dần (Decrescent Generation)
+ Ý tưởng : Cách này đọc qua tưởng chừng như ngược lại
với cách trên nhưng thực sự là có sự khác biệt rất lớn ! Nói chung là chọn một thang đo ban đầu để đánh giá tĩnh sự lợi hại của các nước đi hiện tại cần sinh Sau đó lần lượt giảm thang đo và sinh nước đi cho đến khi nào chứa ít nhất 1 nước
đi trong stack thì break nếu chưa thì sẽ tiếp tục sinh tiếp
+ Phân tích : Thuật toán như sau
Trang 19if (ValueIt(i) == WORTH) // nếu thật sự có giá trị
if (IsNotInGenMove(i)) StoreGenMove(i) ExistAtLeastOneMoveGen = TRUE;
if (ExistAtLeastOenMoveGen) break;
+ Nhận xét: Rõ ràng cách này luôn tìm ra nước đi để đưa vào move-stack từ đó thể hiện sự đúng đắn của nó Kỹ thuật này khá phổ biến trong các chương trình cờ bởi vì nó sẽ lần lượt chọn lựa những nước đi từ tốt đến xấu nhất dựa trên thang đo là giá trị của mark và nó bảo đảm sẽ sinh và chọn
ra những nước tốt nhất có thể từ trạng thái hiện tại để đưa vào stack Tuy nhiên điều này còn phụ thuộc vào hàm ValueIt() nữa Nếu hàm này làm nhanh và chính xác thì sẽ đem hiệu quả cao Cách thiết kế hàm này cũng tương tự như hàm IsHasOutlook() ở trên nhưng cần rộng và chính xác hơn tức là phải tính thêm những nước được xem là "outlook" đối với đối thủ nữa, bởi vì những nước đó có thể thật sự có giá trị (do nếu ta đánh vào nước đó sẽ chiếm vị trí "chiến lược" của đối phương, tức là chặn trước những "đường mở" của đối thủ,
và đôi khi việc làm này là đáng giá hơn việc đánh 1 đường 5 (thuộc loại có triển vọng của ta) mà đối thủ dễ dàng chặn phá) Kỹ thuật này thật sự rất hay nhưng nó vẫn có những nhược điểm sau, đó là việc thiết kế hàm ValueIt() thật sự là rất khó, bởi vì không thể lập trình chính xác cho máy biết được nước nào là có giá trị, đây chỉ là gần đúng mà thôi Hơn nữa, khi thiết kế xong rồi kết hợp nó vào cái hàm tìm kiếm nước đi thì sẽ rất mất thời gian
- Sinh tất cả các nước đi (Complete Generation)
Trang 20+ Ý tưởng : Sử dụng stack để lưu tất cả nước đi của trạng
thái bắt đầu tìm kiếm, sau đó việc tìm kiếm nước đi sẽ không
cần sinh thêm lần nào nữa Tức là chỉ cần sinh 1 lần và cất ngay vào move-stack, sau này khi lấy ra sẽ kiểm tra xem nó
có còn là một nước đi hợp lệ hay không sau đó phân tích nước sinh đó
+ Phân tích :
Thuật toán của kỹ thuật này tương tự Full Generation
+ Nhận xét: Cách này sẽ không tốn thời gian sinh nước đi
trong quá trình tìm kiếm, không tốn nhiều bộ nhớ để lưu các biến nước đi theo từng độ sâu nhưng tốn nhiều thời gian cho việc phân tích và chọn lựa nước đi Với những ưu điểm trên thì kỹ thuật này dường như nó tiến bộ hơn Full generation Tuy nhiên đó là trong trường hợp không kèm thêm việc lượng giá nước đi cùng với việc sinh nước đi, bởi vì việc tìm kiếm chọn lọc nước đi là phải dựa trên chỉ số đánh giá nào, chỉ số
đó được tính ra sao và khi nào, ở đâu
- Sinh nước đi có chọn lọc (Selective Generation)
+Ý tưởng : Dùng một hàm chọn lựa để đánh giá những nước đi nào là đạt những tiêu chuẩn đặt ra thì mới đưa vào stack, rõ ràng hàm chọn lựa này là rất rộng Ví dụ : chỉ sinh những ô trong vòng bán kính 1,2 ô so với những ô đã đánh, chỉ sinh những ô mà có thể gây nên những “đường đe doạ”, chỉ sinh những ô mà có thể chặn được đường hiện tại…Các tiêu chuẩn này hoàn toàn do người lập trình đề ra
+Phân tích : Thuật toán như sau while (ExistMoveGenSatisfiesTheCriteria())
if (IsNotInGenMove(GetGenMove()) StoreGenMove(GetGenMove())
Trang 21+ Nhận xét: Có thể thấy thuật toán khá đơn giản nhưng thật ra là rất phức tạp, từ việc thiết kế những hàm đo tiêu chuẩn cho đến việc kết hợp tất cả hàm đó lại Tuy nhiên cách này sẽ mang lại hiệu quả khá cao dựa trên qui luật bù trừ (ví
dụ nếu tìm kiếm nhanh nhanh thì có thể sinh ra nhiều nước
đi và có thể đánh giá chúng sơ qua, tìm kiếm chậm thì đưa
ra ít nước đi nhưng mỗi nước đi đều đã qua bước chọn lọc kỹ càng, chỉ cần lấy 1 nước đi ra là đã có ngay một nước đi khá tốt) Ngoài ra, nếu phối hợp cách này với những kỹ thuật ở trên thì rất tốt, ví dụ đơn giản là chỉ sinh những nước đi trong vòng bán kính 2 ô so với những ô đã đi và lựa chọn những nước đi thoả mãn thang đo nào đó ( nếu đang ở chế độ tấn công thì sẽ sinh những nước gây ra đường đe doạ còn ngược lại đang ở chế độ phòng thủ thì chọn những nước có triển vọng của đối phương mà đánh) Khi đó, số lượng những nước
đi thoả các tiêu chuẩn trên sẽ không nhiều (tức là độ fân nhánh thấp) mà chất lượng của nước đi sẽ được nâng cao (do toàn lựa những nước đi có quan hệ với những nước đi đã đánh, khi đó ta còn có thể gia tăng độ sâu lên thêm vài nấc
b.Kỹ thuật “Lượng giá” (Evaluate)
- Cài đặt CTDL để lưu cấu hình của bàn cờ :
+Ý tưởng :
- Đầu tiên xét 1 ô trống trên bàn cờ, khi đó, giả sử ta chỉ kiểm tra trên phương nằm ngang (các phương khác tương tự) bao gồm n ô liên tiếp bên trái và n ô liên tiếp bên phải của nó (tức là xét trên 1 hàng ngang có chiều dài 2n+1 ô liên tiếp, khi
đó ta có thể thấy được vài điều sau để tóm gọn lại vấn đề :
+ Ta phải xem xét ô trống này trên 2 phương diện : đối
Trang 22với quân ta hay đối với quân của đối thủ, do đó lúc này thì cách làm cho mỗi bên là tương tự nhau nên chọn quân ta (quân O) + Việc xét n ô bên trái hay bên phải thì cách làm cũng tương tự nhau (cùng xuất phát từ vị trí ô trống đang xét nhưng chỉ khác nhau về hướng), do đó giả sử chỉ xét một bên (trái) + Với qui ước :
+ n = 4 và đang kiểm tra cho quân O
+ Dấu X,O : ô đã bị chiếm bởi quân X,O
+ Dấu * : ô trống trên bàn cờ
+ Dấu # : biên của bàn cờ
+ Dấu ? : ô trống đang xét
thì cấu hình của n ô đó có thể như sau :
- Không có ô nào đáng để ta quan tâm nữa :
Ví dụ : Xét 1 cấu hình trên bàn cờ :
*{/color}*OOOX{/color}?OOO** : Khi đó ta kiểm tra từ phải sang trái (bắt đầu từ dấu '?'), gặp ngay quân X kế tiếp Do đó lúc này dù 3 ô kế tiếp đều chứa quân O nhưng đối với ô trống đang xét thì nó chẳng có giá trị gì Tức là bên trái không còn ô nào đáng được quan tâm Điều này cũng xảy ra khi ô trống đang xét là nằm kế biên hoặc là bên trái của nó đã chứa >= 5 con (đúng 5 con mới được ăn)
- Chỉ có 1 ô cần quan tâm
Ví dụ : Xét các cấu hình sau : 1) #*?OO** và 2) **OOX*?**
và 3) OXO?OO** : Khi đó (1) và (2) chỉ còn 1 ô trống kế tiếp bên trái, (3) chỉ còn 1 quân cờ 'O' kế tiếp bên trái.Tức là còn đúng 1 ô cần quan tâm
- Chỉ còn 2 ô cần quan tâm
Ví dụ : Xét : 1) OX*O?**O* và 2) #**?OO** và 3) OXO*?O**
Trang 23const char StTable[31][5] = {
"####", // Không còn ô nào đáng quan tâm
"*###","O###", //Có một ô đáng quan tâm
"**##","*O##","O*##","OO##",//Có hai ô
"***#","**O#","*O*#","*OO#","O**#","O*O#","OO*#","OOO#",//Có ba ô
"****","***O","**O*","**OO","*O**","*O*O","*OO*","*OOO", //Có bốn ô
"O***","O**O","O*O*","O*OO","OO**","OO*O","OOO*","OOOO"};
* Phân tích một vài các mẫu : Hãy xét từ trái sang phải : -StTable[0] ("####") : Không có ô nào đáng quan tâm
Trang 24vì nó đã bị chiếm bởi quân của đối thủ hoặc là ô nằm kế cạnh của bàn cờ
-StTable[1] ("*###") : Chỉ còn 1 ô trống kế tiếp, còn lại không đáng quan tâm
-StTable[2] ("OO##") : Hai ô kế tiếp (trên hướng đang xét, ví dụ bên trái) là quân ta, ngoài ra không còn ô nào đáng quan tâm
-StTable[30]("OOOO") : Có bốn quân cờ bên ta liên tiếp Định nghĩa theo kiểu này là rất trực quan, thể hiện các cấu hình có thể có với bốn ô liên tiếp trên bàn cờ, mỗi phần
tử của 1 mảng StTable là một chuỗi có chiều dài không đổi
và bằng 4 Nhưng mảng này chỉ có ý nghĩa để trình bày trên màn hình, nếu không, cách cài đặt theo CTDL này sẽ rất khó để xử lý và cải tiến : chỉ lưu con số chỉ vị trí của từng cấu hình, ví dụ nếu có số 3 (bắt đầu từ 0) => đó là cấu hình
"*###", có số 30 => "OOOO" nên cần 1 biến kiểu int Đặt tên đại là "Cfg Bên trái/phải"
+ Mỗi ô có 2 phía, mỗi phía là 1 con số có nghĩa từ 0 30 như trên Do đó ta có :
typedef struct CELL_PATTERN {
int nBackward; /* nBackward = 0 30 <== Left side */
int nForward; /* nForward = 0 30 <== Right side */
} PATTERN_TYPE,*PPATTERN_TYPE;
typedef PATTERN_TYPE PatternType;
+ Mỗi ô lại còn được xét trên 4 phương:
(-)ngang(horizontal),(|)dọc(vertical), (\)chéo chính(main diagonal) và (/)chéo phụ(sub diagonal)
Mỗi ô phải lưu bốn thống tin như vậy Và cuối cùng là cho cả
2 bên : quân ta và đối phương PatternType LinePattern[2][BOARD_SIZE*4];
Trang 25- Như vậy xét trên 1 phương thì ta sẽ có tổ hợp 31x31 cấu hình có thể có cho 1 ô
Ví dụ : + Cặp (0,29) biểu thị cho cấu hình 9 ô liên tiếp như sau :
const char StTable[31][5] = { "####",
"*###","O###", "**##","*O##","O*##","OO##", "***#","**O#","*O*#","*OO#","O**#","O*O#","OO*#","OOO#", "****","***O","**O*","**OO","*O**","*O*O","*OO*","*OOO", "O***","O**O","O*O*","O*OO","OO**","OO*O","OOO*","OOOO"}; void main()
{ ofstream out_all("Out.txt");
for (int i = 0;i < 31;i++) {
for (int j = 0;j < 31;j++) {
out_all << "(" << i << "," << j << ")" << "\t: |"; for (int k = 3;k >= 0;k )
out_all << StTable[i][k] << flush;
out_all << char(0x19) << flush;
Trang 26out_all << StTable[j] << "|" << endl;
} } } // end
- Làm việc với CTDL này :
- Việc truy xuất đến CTDL là không khó, vì chỉ cần biết nForward Và nBackward là biết ngay nó đang có dạng (configuration) như thế nào, sau đó tuỳ theo dạng này mà lượng giá Do đó mục tiêu khó khăn đặt ra là phải cập nhật các cấu hình của các ô sau mỗi nước đi của cả 2 bên sao cho cấu hình này phản ánh đúng và chính xác cấu hình thật sự của từng ô trống trên bàn cờ Việc làm này cũng phải nhanh
vì nó sẽ nằm trong phần gọi đệ qui Các cài đặt như sau:
+ Khai báo 1 mảng để lưu các biến đổi từ cấu hình này sang cấu hình khác sau khi một ô trống bị thay đổi trạng thái (tức là thành quân ta hay quân đối phương), mảng này sẽ có nhiệm vụ chuyển từ một cấu hình có sẵn sang một cấu hình mới
+ Bây giờ giả sử ta xét trên 1 hàng ngang 9 ô, ô thứ 5 (ở
chính giữa) là ô trống đang xét), 4 ô ở bên trái (từ 1 đến 4)
và 4 ô ở bên phải (từ 6 đến 9) tương ứng là một cặp cấu hình (trong số tổ hợp 31 cấu hình ở trên) sẽ có dạng sau
-Vị trí đang xét là ô trống thứ 5 (hình vuông)
Vị trí : 1 2 3 4 5 6 7 8 9 Mảng a[i] : x x x x x x x x
Trong đó x là 1 trạng thái chưa biết(nằm trong tập : {O,X,#,*}) nhưng không quan trọng vì ta chỉ quan tâm các cấu hình dưới dạng các con số mà thôi!(Rõ ràng là vậy vì 4 chữ x bên trái ô vuông là thành phần nBackward, 4 chữ x bên phải là nForward, cả nBackward & nForward đều là 1 con