나는 하나의 게임 진드기에 대해 가능한 모든 상태를 생성 할 수 있다고 생각하지만 4 명의 플레이어와 5 개의 기본 액션 (4 이동 및 폭탄 장소)으로 게임 트리의 첫 번째 레벨에서 5 ^ 4 상태를 제공합니다.
옳은! 각 게임 틱에 대해 5 ^ 4 (또는 4 ^ 방향으로 걸을 수 있고 폭탄을 멈출 수있는 6? 4)까지 모두 검색해야합니다. 그러나 플레이어가 이미 이동하기로 결정한 경우 이동이 실행될 때까지 시간이 걸립니다 (예 : 10 게임 틱). 이 기간 동안 가능성의 수가 줄어 듭니다.
그 가치는 다음 단계마다 기하 급수적으로 증가 할 것입니다. 뭔가 빠졌습니까? 그것을 구현하는 방법이 있습니까? 아니면 완전히 다른 알고리즘을 사용해야합니까?
해시 테이블을 사용하여 동일한 게임 상태 "하위 트리"를 한 번만 계산할 수 있습니다. 플레이어 A가 위 아래로 걷는다고 상상해보십시오. 다른 모든 플레이어는 "기다리면서"같은 게임 상태에있게됩니다. "왼쪽-오른쪽"또는 "오른쪽-왼쪽"과 동일합니다. "왼쪽 위로"및 "왼쪽 위로"를 이동해도 동일한 상태가됩니다. 해시 테이블을 사용하면 이미 평가 된 게임 상태에 대해 계산 된 점수를 "재사용"할 수 있습니다. 이것은 성장 속도를 상당히 줄입니다. 수학적으로 지수 성장 함수의 기초를 줄입니다. 플레이어가 단지 위 / 아래 / 왼쪽 / 오른쪽 / 정지 위치로 이동할 수있는 경우 복잡도를 얼마나 줄이는 지 이해하기 위해 맵에서 도달 가능한 위치 (= 다른 게임 상태)와 비교하여 한 명의 플레이어 만 가능한 움직임을 살펴 보겠습니다. .
깊이 1 : 5 회 이동, 5 개의 다른 상태,이 재귀에 대한 5 개의 추가 상태
깊이 2 : 25 회 이동, 13 개 상태,이 재귀에 대해 8 개의 추가 상태
깊이 3 : 6125 이동, 25 개의 다른 상태,이 재귀에 대한 12 개의 추가 상태
이를 시각화하려면 자신에게 답하십시오. 한 번의 이동, 두 번의 이동, 세 번의 이동으로지도의 어떤 필드에 도달 할 수 있습니까? 답은 : 시작 위치에서 최대 거리 = 1, 2 또는 3 인 모든 필드입니다.
HashTable을 사용할 때는 도달 가능한 각 게임 상태 (예 : 25의 깊이 3)를 한 번만 평가하면됩니다. HashTable이 없으면 깊이 수준 3에서 25 대신 6125 평가를 의미하는 여러 번 평가해야합니다. 최고 : HashTable 항목을 계산 한 후에는 나중에 단계별로 다시 사용할 수 있습니다 ...
더 깊이 검색 할 가치가없는 증분 심화 및 알파-베타 가지 치기 "잘라 내기"하위 트리를 사용할 수도 있습니다. 체스의 경우 이것은 검색된 노드 수를 약 1 %로 줄입니다. 알파-베타 가지 치기에 대한 짧은 소개는 여기 비디오에서 찾을 수 있습니다 : http://www.teachingtree.co/cs/watch?concept_name=Alpha-beta+Pruning
추가 연구를위한 좋은 시작은 http://chessprogramming.wikispaces.com/Search 입니다. 이 페이지는 체스와 관련이 있지만 검색 및 최적화 알고리즘은 매우 동일합니다.
게임에 더 적합한 다른 (그러나 복잡한) 인공 지능 알고리즘은 "임시 차이 학습"입니다.
문안 인사
스테판
추신 : 가능한 게임 상태의 수를 줄이면 (예를 들어, 맵의 매우 작은 크기, 플레이어 당 하나의 폭탄 만, 다른 것은 없음) 모든 게임 상태에 대한 평가를 미리 계산할 수 있습니다.
--편집하다--
오프라인 계산 된 minimax 계산 결과를 사용하여 뉴런 네트워크를 학습 할 수도 있습니다. 또는이를 사용하여 손으로 구현 한 전략을 평가 / 비교할 수 있습니다. 예를 들어, 제안 된 "특성"과 감지 할 수있는 휴리스틱을 구현할 수 있으며, 어떤 상황에서 전략이 좋은지 알 수 있습니다. 따라서 상황을 "분류"해야합니다 (예 : 게임 상태). 이것은 뉴런 네트워크에 의해 처리 될 수도 있습니다 : 뉴런 네트워크를 훈련시켜 현재 상황에서 어떤 핸드 코딩 전략이 가장 잘 실행되고 있는지를 예측하고 실행하십시오. 이것은 실제 게임에 대해 매우 좋은 실시간 결정을 내려야합니다. 오프라인 계산에 걸리는 시간 (게임 이전)은 중요하지 않기 때문에 달리 수행 할 수있는 심도 제한 검색보다 훨씬 낫습니다.
-편집 # 2-
1 초마다 최고의 움직임 만 다시 계산하면 더 높은 수준의 계획을 시도 할 수도 있습니다. 그게 무슨 소리 야? 1 초 동안 얼마나 많은 움직임을 할 수 있는지 알고 있습니다. 따라서 도달 가능한 위치 목록을 만들 수 있습니다 (예 : 1 초에 3 번 이동하면 도달 가능한 위치는 25입니다). 그런 다음 다음과 같이 계획 할 수 있습니다. "위치 x로 이동하여 폭탄을 배치하십시오". 다른 사람들이 제안한 것처럼 라우팅 알고리즘에 사용되는 "위험"맵을 생성 할 수 있습니다 (x로 이동하는 방법-어떤 경로를 선호해야합니까 (대부분의 경우 가능한 변형이 있음)). 이것은 거대한 HashTable과 비교할 때 메모리 소비가 적지 만 최적의 결과를 얻지 못합니다. 그러나 적은 메모리를 사용하므로 캐싱 효과 (L1 / L2 메모리 캐시를 더 잘 사용)로 인해 더 빠를 수 있습니다.
또한 모두 : 한 선수의 움직임 만 포함하는 사전 검색을 수행하여 결과를 잃어버린 변형을 정렬 할 수 있습니다. 따라서 모든 다른 플레이어를 게임에서 꺼내십시오. 각 플레이어가 잃어 버리지 않고 선택할 수있는 조합. 잃어버린 움직임 만 있다면 플레이어가 가장 오래 살아남는 움직임 조합을 찾으십시오. 이런 종류의 트리 구조를 저장 / 처리하려면 다음과 같은 인덱스 포인터가있는 배열을 사용해야합니다.
class Gamestate {
int value;
int bestmove;
int moves[5];
};
#define MAX 1000000
Gamestate[MAX] tree;
int rootindex = 0;
int nextfree = 1;
각 상태는 평가 "값"을 가지며 이동 중에 "트리"내에 배열 인덱스를 저장하여 이동할 때 (0 = 중지, 1 = 위쪽, 2 = 오른쪽, 3 = 아래쪽, 4 = 왼쪽) 다음 게임 상태에 연결됩니다 [0 ]를 이동합니다 [4]. 재귀 적으로 나무를 만들려면 다음과 같이 보일 수 있습니다.
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;
}
동적으로 메모리를 할당하는 것이 실제로 느리기 때문에 이런 종류의 트리 구조는 훨씬 빠릅니다! 그러나 검색 트리를 저장하는 것도 상당히 느립니다. 따라서 이것은 더 많은 영감을줍니다.