Tôi nghĩ rằng tôi có thể tạo ra tất cả các trạng thái có thể cho một tích tắc trò chơi, nhưng với bốn người chơi và 5 hành động cơ bản (4 lần di chuyển và đặt bom), nó mang lại 5 ^ 4 trạng thái ở cấp độ đầu tiên của cây trò chơi.
Chính xác! Bạn cần tìm kiếm tất cả 5 ^ 4 (hoặc thậm chí 6 ^ 4, vì bạn có thể đi bộ theo 4 hướng, dừng lại và "đặt bom"?) Cho mỗi lần đánh dấu trò chơi. NHƯNG, khi một người chơi đã quyết định di chuyển, phải mất một thời gian cho đến khi việc di chuyển được thực hiện (ví dụ: 10 tích tắc trò chơi). Trong giai đoạn này số lượng khả năng giảm.
Giá trị đó sẽ tăng theo cấp số nhân với mọi cấp độ tiếp theo. Tui bỏ lỡ điều gì vậy? Có cách nào để thực hiện nó hay tôi nên sử dụng thuật toán hoàn toàn khác nhau?
Bạn có thể sử dụng Bảng Hash để chỉ tính toán trạng thái trò chơi "phụ" trong cùng một lần. Hãy tưởng tượng người chơi A đi lên và xuống, trong khi tất cả những người chơi khác "chờ", bạn kết thúc trong cùng một trạng thái trò chơi. Nó giống như "trái phải" hoặc "phải trái". Cũng di chuyển kết quả "lên-sau-trái" và "trái-sau-lên" trong cùng một trạng thái. Sử dụng Bảng băm, bạn có thể "sử dụng lại" điểm được tính cho trạng thái trò chơi đã được đánh giá. Điều này làm giảm tốc độ tăng trưởng khá nhiều. Về mặt toán học, nó làm giảm cơ sở của hàm tăng trưởng theo cấp số nhân của bạn. Để có được ý tưởng về mức độ giảm độ phức tạp, chúng ta hãy xem xét các động tác có thể chỉ cho một người chơi so với các vị trí có thể tiếp cận trên bản đồ (= các trạng thái trò chơi khác nhau) nếu người chơi chỉ có thể di chuyển lên / xuống / trái / phải / dừng .
độ sâu 1: 5 di chuyển, 5 trạng thái khác nhau, 5 trạng thái bổ sung cho đệ quy này
độ sâu 2: 25 di chuyển, 13 trạng thái khác nhau, 8 trạng thái bổ sung cho đệ quy này
độ sâu 3: 6125 di chuyển, 25 trạng thái khác nhau, 12 trạng thái bổ sung cho đệ quy này
Để hình dung điều đó, hãy tự trả lời: những trường nào trên bản đồ có thể đạt được bằng một lần di chuyển, hai lần di chuyển, ba lần di chuyển. Câu trả lời là: Tất cả các trường có khoảng cách tối đa = 1, 2 hoặc 3 từ vị trí bắt đầu.
Khi sử dụng HashTable, bạn chỉ phải đánh giá từng trạng thái trò chơi có thể tiếp cận (trong ví dụ 25 của chúng tôi ở độ sâu 3) một lần. Trong khi không có HashTable, bạn cần đánh giá chúng nhiều lần, điều đó có nghĩa là 6125 đánh giá thay vì 25 ở cấp độ sâu 3. Tốt nhất: Khi bạn đã tính một mục HashTable, bạn có thể sử dụng lại trong các bước sau ...
Bạn cũng có thể sử dụng các cây con "cắt" tăng cường độ sâu và cắt tỉa alpha-beta mà không đáng để tìm kiếm sâu hơn. Đối với cờ vua, điều này làm giảm số lượng nút tìm kiếm xuống còn khoảng 1%. Giới thiệu ngắn về cách cắt tỉa alpha-beta có thể được tìm thấy dưới dạng video tại đây: http://www.teachingtree.co/cs/watch?concept_name=Alpha-beta+Pruning
Một khởi đầu tốt cho các nghiên cứu tiếp theo là http://chessprogramming.wikispaces.com/Search . Trang này có liên quan đến cờ vua, nhưng các thuật toán tìm kiếm và tối ưu hóa hoàn toàn giống nhau.
Một thuật toán AI (nhưng phức tạp) khác - sẽ phù hợp hơn với trò chơi - là "Học khác biệt tạm thời".
Trân trọng
Stefan
Tái bút: Nếu bạn giảm số lượng trạng thái trò chơi có thể có (ví dụ kích thước bản đồ rất nhỏ, chỉ một quả bom cho mỗi người chơi, không có gì khác), có thể tính toán trước một đánh giá cho tất cả các trạng thái trò chơi.
--biên tập--
Bạn cũng có thể sử dụng kết quả tính toán ngoại tuyến của các tính toán minimax để huấn luyện mạng nơ ron. Hoặc bạn có thể sử dụng chúng để đánh giá / so sánh các chiến lược được thực hiện bằng tay. Ví dụ, bạn có thể thực hiện một số "tính cách" được đề xuất và một số phương pháp phỏng đoán phát hiện, trong đó tình huống nào là chiến lược tốt. Do đó, bạn nên "phân loại" các tình huống (ví dụ: trạng thái trò chơi). Điều này cũng có thể được xử lý bởi một mạng nơ-ron thần kinh: Huấn luyện một mạng nơ-ron thần kinh để dự đoán chiến lược mã hóa nào đang chơi tốt nhất trong tình huống hiện tại và thực hiện nó. Điều này sẽ tạo ra các quyết định thời gian thực cực kỳ tốt cho một trò chơi thực sự. Tốt hơn nhiều so với tìm kiếm giới hạn độ sâu thấp có thể đạt được bằng cách khác, vì việc tính toán ngoại tuyến mất bao lâu (chúng ở trước trò chơi).
- chỉnh sửa số 2 -
Nếu bạn chỉ tính toán lại các bước di chuyển tốt nhất của mình sau mỗi 1 giây, bạn cũng có thể cố gắng thực hiện các kế hoạch cấp cao hơn. Ý tôi là gì? Bạn biết có bao nhiêu động tác bạn có thể làm trong 1 giây. Vì vậy, bạn có thể lập danh sách các vị trí có thể tiếp cận (ví dụ: nếu đây là 3 lần di chuyển trong 1 giây, bạn sẽ có 25 vị trí có thể tiếp cận). Sau đó, bạn có thể lên kế hoạch như: đi đến "vị trí x và đặt bom". Như một số người khác đề nghị bạn có thể tạo bản đồ "nguy hiểm", được sử dụng cho thuật toán định tuyến (làm thế nào để đi đến vị trí x? Nên chọn đường dẫn nào [có một số biến thể có thể xảy ra trong hầu hết các trường hợp]). Điều này ít tiêu tốn bộ nhớ hơn so với HashTable khổng lồ, nhưng tạo ra kết quả ít tối ưu hơn. Nhưng vì nó sử dụng ít bộ nhớ hơn nên nó có thể nhanh hơn do hiệu ứng bộ đệm (sử dụng tốt hơn bộ nhớ đệm L1 / L2 của bạn).
BỔ SUNG: Bạn có thể thực hiện các tìm kiếm trước chỉ chứa các di chuyển cho mỗi người chơi để sắp xếp các biến thể dẫn đến mất. Do đó, đưa tất cả những người chơi khác ra khỏi trò chơi ... Lưu trữ những kết hợp mà mỗi người chơi có thể chọn mà không mất. Nếu chỉ có các bước di chuyển bị mất, hãy tìm kiếm các kết hợp di chuyển mà người chơi vẫn sống lâu nhất. Để lưu trữ / xử lý loại cấu trúc cây này, bạn nên sử dụng một mảng với các con trỏ chỉ mục như thế này:
class Gamestate {
int value;
int bestmove;
int moves[5];
};
#define MAX 1000000
Gamestate[MAX] tree;
int rootindex = 0;
int nextfree = 1;
Mỗi trạng thái có một "giá trị" đánh giá và liên kết đến các Gamestates tiếp theo khi di chuyển (0 = stop, 1 = up, 2 = right, 3 = down, 4 = left) bằng cách lưu trữ chỉ mục mảng trong "cây" trong di chuyển [0 ] để di chuyển [4]. Để xây dựng cây của bạn một cách đệ quy, nó có thể trông như thế này:
const int dx[5] = { 0, 0, 1, 0, -1 };
const int dy[5] = { 0, -1, 0, 1, 0 };
int search(int x, int y, int current_state, int depth_left) {
// TODO: simulate bombs here...
if (died) return RESULT_DEAD;
if (depth_left == 0) {
return estimate_result();
}
int bestresult = RESULT_DEAD;
for(int m=0; m<5; ++m) {
int nx = x + dx[m];
int ny = y + dy[m];
if (m == 0 || is_map_free(nx,ny)) {
int newstateindex = nextfree;
tree[current_state].move[m] = newstateindex ;
++nextfree;
if (newstateindex >= MAX) {
// ERROR-MESSAGE!!!
}
do_move(m, &undodata);
int result = search(nx, ny, newstateindex, depth_left-1);
undo_move(undodata);
if (result == RESULT_DEAD) {
tree[current_state].move[m] = -1; // cut subtree...
}
if (result > bestresult) {
bestresult = result;
tree[current_state].bestmove = m;
}
}
}
return bestresult;
}
Kiểu cấu trúc cây này nhanh hơn nhiều, vì bộ nhớ phân bổ động rất chậm! Nhưng, việc lưu trữ cây tìm kiếm cũng khá chậm ... Vì vậy, đây là một nguồn cảm hứng nhiều hơn.