분류
대답은 쉽지 않습니다. 게임 이론에는 게임에 대한 분류가 있지만, 해당 게임과 특별한 이론이 1 : 1로 일치하지 않는 것 같습니다. 특별한 형태의 조합 문제입니다.
세일즈맨은 여행하지 않으며, 마지막 노드에서 다음 노드에 도달하는 데 약간의 비용이 "노드"를 방문하는 순서를 결정합니다. 대기열을 다시 정렬 할 수 없으며지도에서 모든 필드를 사용해야 할 수도 있습니다.
일부 항목을 "백팩"에 넣는 동안 일부 필드가 비어 있기 때문에 배낭이 일치하지 않습니다. 확장 된 형태 일 수도 있지만, 이로 인해 알고리즘을 적용 할 수 없을 것입니다.
Wikipedia는 분류에 대한 힌트를 제공합니다 : http://en.wikipedia.org/wiki/Game_theory#Types_of_games
나는 이것을 "이산 시간 최적의 제어 문제"( http://en.wikipedia.org/wiki/Optimal_control )로 분류하지만 이것이 도움이 될 것이라고 생각하지 않습니다.
알고리즘
전체 대기열을 실제로 알고있는 경우 트리 검색 알고리즘을 적용 할 수 있습니다. 당신이 말했듯이, 문제의 복잡성은 대기열 길이에 따라 매우 빠르게 증가합니다. 많은 메모리를 필요로하지 않는 "DFS (Depth-first search)"와 같은 알고리즘을 사용하는 것이 좋습니다. 점수가 중요하지 않기 때문에 첫 번째 해결책을 찾은 후에 멈출 수 있습니다. 먼저 검색 할 하위 브랜치를 결정하려면 주문에 대해 휴리스틱을 적용해야합니다. 즉, 평가 필드를 작성해야합니다 (예 : 빈 필드 수,이 필드가 더 정교할수록 더 우수). 다음으로 예상되는 움직임이 가장 유망합니다.
그런 다음 다음 부분 만 필요합니다.
- 게임의 모든 정보를 저장하는 게임 상태 모델 (예 : 보드 상태 /지도, 대기열, 대기열에서 이동 번호 / 위치)
- 주어진 게임 상태에 대한 모든 유효한 이동을 제공하는 이동 생성기
- "이동"및 "실행 취소"기능; 주어진 (유효한) 이동을 게임 상태로 적용 / 실행 취소합니다. "이동"기능은 "실행 취소"기능에 대한 "실행 취소 정보"를 저장해야합니다. 게임 상태를 복사하고 반복 할 때마다 수정하면 검색 속도가 크게 느려집니다! 최소한 스택에 상태를 저장하십시오 (= 로컬 변수, "new"를 사용한 동적 할당 없음).
- 각 게임 상태에 대해 비슷한 점수를주는 평가 기능
- 검색 기능
깊이 우선 검색을위한 불완전한 참조 구현은 다음과 같습니다.
public class Item
{
// TODO... represents queue items (FLOWER, SHOVEL, BUTTERFLY)
}
public class Field
{
// TODO... represents field on the board (EMPTY or FLOWER)
}
public class Modification {
int x, y;
Field originalValue, newValue;
public Modification(int x, int y, Field originalValue, newValue) {
this.x = x;
this.y = y;
this.originalValue = originalValue;
this.newValue = newValue;
}
public void Do(GameState state) {
state.board[x,y] = newValue;
}
public void Undo(GameState state) {
state.board[x,y] = originalValue;
}
}
class Move : ICompareable {
// score; from evaluation function
public int score;
// List of modifications to do/undo to execute the move or to undo it
Modification[] modifications;
// Information for later knowing, what "control" action has been chosen
public int x, y; // target field chosen
public int x2, y2; // secondary target field chosen (e.g. if moving a field)
public Move(GameState state, Modification[] modifications, int score, int x, int y, int x2 = -1, int y2 = -1) {
this.modifications = modifications;
this.score = score;
this.x = x;
this.y = y;
this.x2 = x2;
this.y2 = y2;
}
public int CompareTo(Move other)
{
return other.score - this.score; // less than 0, if "this" precededs "other"...
}
public virtual void Do(GameState state)
{
foreach(Modification m in modifications) m.Do(state);
state.queueindex++;
}
public virtual void Undo(GameState state)
{
--state.queueindex;
for (int i = m.length - 1; i >= 0; --i) m.Undo(state); // undo modification in reversed order
}
}
class GameState {
public Item[] queue;
public Field[][] board;
public int queueindex;
public GameState(Field[][] board, Item[] queue) {
this.board = board;
this.queue = queue;
this.queueindex = 0;
}
private int Evaluate()
{
int value = 0;
// TODO: Calculate some reasonable value for the game state...
return value;
}
private List<Modification> SimulateAutomaticChanges(ref int score) {
List<Modification> modifications = new List<Modification>();
// TODO: estimate all "remove" flowers or recoler them according to game rules
// and store all changes into modifications...
if (modifications.Count() > 0) {
foreach(Modification modification in modifications) modification.Do(this);
// Recursively call this function, for cases of chain reactions...
List<Modification> moreModifications = SimulateAutomaticChanges();
foreach(Modification modification in modifications) modification.Undo(this);
// Add recursively generated moves...
modifications.AddRange(moreModifications);
} else {
score = Evaluate();
}
return modifications;
}
// Helper function for move generator...
private void MoveListAdd(List<Move> movelist, List<Modifications> modifications, int x, int y, int x2 = -1, int y2 = -1) {
foreach(Modification modification in modifications) modification.Do(this);
int score;
List<Modification> autoChanges = SimulateAutomaticChanges(score);
foreach(Modification modification in modifications) modification.Undo(this);
modifications.AddRange(autoChanges);
movelist.Add(new Move(this, modifications, score, x, y, x2, y2));
}
private List<Move> getValidMoves() {
List<Move> movelist = new List<Move>();
Item nextItem = queue[queueindex];
const int MAX = board.length * board[0].length + 2;
if (nextItem.ItemType == Item.SHOVEL)
{
for (int x = 0; x < board.length; ++x)
{
for (int y = 0; y < board[x].length; ++y)
{
// TODO: Check if valid, else "continue;"
for (int x2 = 0; x2 < board.length; ++x2)
{
for(int y2 = 0; y2 < board[x].length; ++y2) {
List<Modifications> modifications = new List<Modifications>();
Item fromItem = board[x][y];
Item toItem = board[x2][y2];
modifications.Add(new Modification(x, y, fromItem, Item.NONE));
modifications.Add(new Modification(x2, y2, toItem, fromItem));
MoveListAdd(movelist, modifications, x, y, x2, y2);
}
}
}
}
} else {
for (int x = 0; x < board.length; ++x)
{
for (int y = 0; y < board[x].length; ++y)
{
// TODO: check if nextItem may be applied here... if not "continue;"
List<Modifications> modifications = new List<Modifications>();
if (nextItem.ItemType == Item.FLOWER) {
// TODO: generate modifications for putting flower at x,y
} else {
// TODO: generate modifications for putting butterfly "nextItem" at x,y
}
MoveListAdd(movelist, modifications, x, y);
}
}
}
// Sort movelist...
movelist.Sort();
return movelist;
}
public List<Move> Search()
{
List<Move> validmoves = getValidMoves();
foreach(Move move in validmoves) {
move.Do(this);
List<Move> solution = Search();
if (solution != null)
{
solution.Prepend(move);
return solution;
}
move.Undo(this);
}
// return "null" as no solution was found in this branch...
// this will also happen if validmoves == empty (e.g. lost game)
return null;
}
}
이 코드는 작동하지 않으며 컴파일 가능하거나 완료되지 않았습니다. 그러나 그것을 수행하는 방법에 대한 아이디어를 제공해야합니다. 가장 중요한 작업은 평가 기능입니다. 더 정교할수록 알고리즘이 나중에 시도하고 실행 취소해야하는 "시도"가 잘못됩니다. 이것은 복잡성을 크게 줄입니다.
이것이 너무 느리면 HashTables로 두 사람 게임의 일부 방법을 적용 할 수도 있습니다. 이를 위해서는 평가하는 각 게임 상태에 대해 (반복적) 해시 키를 계산하고 솔루션으로 이어지지 않는 상태를 표시해야합니다. 예를 들어 Search () 메소드가 "null"을 리턴하기 전에는 항상 HashTable 항목을 작성해야하며 Search ()를 입력 할 때이 상태에 아직 긍정적 인 결과가없는 상태에 도달했는지 확인하고 그렇지 않은 경우 "null"을 리턴합니다. 추가 조사. 이를 위해서는 거대한 해시 테이블이 필요하며 "해시 충돌"을 받아 들여야합니다. 이로 인해 기존 솔루션을 찾지 못할 수도 있지만 해시 함수가 충분하고 테이블이 충분히 큰 (계산 가능한 위험의 위험).
더 효율적 으로이 문제를 해결할 수있는 다른 알고리즘은 없다고 생각합니다. 평가 기능이 최적이라고 가정합니다 ...