// Função Start() do Unity, executada ao iniciar a execução do programa void Start() { // Obtendo a Referência do Agente player = HyruleMapManager.getInstance().getPlayerReference(); // Comentários auxiliares durante a implementação // 1. add to stack -> lost woods, dungeons 1, 2, 3 and house, dungeons are the travelling salesman problem // 2. on the begginig the stack will be: lostWoords | link's house | dungeon | dungeon | dungeon // 3. remove first on the stack and A* to it // 4. when reach dungeon, will go inside it, add to stack -> exit | get pendant // 5. go to 3. // sample execution: // Init stack. -> stack: lost woods | house | dungeon a | dungeon b | dungeon c // go to dungeon c -> stack: lost woods | house | dungeon a | dungeon b | exit dungeon c | get pendant c // get pendant c -> stack: lost woods | house | dungeon a | dungeon b | exit dungeon c // exit dungeon c -> stack: lost woods | house | dungeon a | dungeon b // go to dungeon b -> stack: lost woods | house | dungeon a | exit dungeon b | get pendant b // get pendant b -> stack: lost woods | house | dungeon a | exit dungeon b // exit dungeon b -> stack: lost woods | house | dungeon a // go to dungeon a -> stack: lost woods | house | exit dungeon a | get pendant a // get pendant a -> stack: lost woods | house | exit dungeon a // exit dungeon a -> stack: lost woods | house // go to house -> stack: lost woods // go to lost woods // the end // for each iteration -> remove objective from stack, A* until reach it, when accomplished, get next objective /* ALGORITHM: * while(objectivesStack is not empty) * * if(!objective) * objective = objectivesStack.pop(); * * while(!reached_object) * calculate_path_to_objective_using_A*() * execute_commands_and_go_to_objective() * delete(objective) * * * * calculate_path_to_objective_using_A*(): * calculate_heuristics_on_map() * * evaluated_node = current_node * while(evaluting_node != goal_node) * foreach(neighbour_tiles) * select_nodes_to_evaluate() * get(f(n)) * push them into the queue() * evaluating_node = queueNext * */ // Função que dará inicio aos planejamentos e ações do agente //saveHyrule (); }
/* * * Função de inserir movimentos na lista de movimentos do agente * * Recebe o nodo objetivo que foi atingido através da busca * * Através da informação do nodo e de seu pai é possivel obter o movimento que o agente * deve realizar para ir do nodo pai para o filho. * * É executado um laço percorrendo os pais dos nodos até que seja encontrada a raiz (pai nulo) */ private void insertMovement(Tile goal) { //Debug.Log ("found goal!"); // temos o objetivo, agora podemos atualizar os custos dos caminhos! custoCaminho = goal.getG(); custoAcumulado = custoAcumulado + custoCaminho; UserInterface.getInstance().setCaminhoParcial(custoCaminho); UserInterface.getInstance().setCaminhoTotal(custoAcumulado); Tile p = goal; Vector2 pos; while (p.getParent() != null) { // Marca na interface que essa posição faz parte do caminho final #if VISUALIZE_ASTAR_STEPS HyruleMapManager.getInstance().markPath((int)p.getPosition().x, (int)p.getPosition().y); #endif // obtém a diferença entre a posição atual e do pai pos = new Vector2(p.getPosition().x - p.getParent().getPosition().x, p.getPosition().y - p.getParent().getPosition().y); //Debug.Log ("p position: " + p.getPosition()); //Debug.Log ("p parent position: " + p.getParent ().getPosition ()); //Debug.Log ("pos: " + pos); // de acordo com a posição obtida calcula o movimento que o agente terá que fazer // e empilha esse movimento if (pos == Vector2.up) { //Debug.Log ("up: " + pos); movements.Insert(0, 'R'); } else if (pos == Vector2.down) { //Debug.Log ("down: " + pos); movements.Insert(0, 'L'); } else if (pos == Vector2.left) { //Debug.Log ("left: " + pos); movements.Insert(0, 'U'); } else if (pos == Vector2.right) { //Debug.Log ("right:" + pos); movements.Insert(0, 'D'); } p = p.getParent(); } }
/* * Função que movimenta o agente, recebe uma lista de movimentos * * Para cada movemento da lista de movimentos, o agente efetivamente realiza o movimento * É a função que transforma todo o planejamento em ação * */ private IEnumerator move_to(List <char> moveSequence) { //Debug.Log ("movimentos: " + moveSequence.Count); for (int i = 0; i < moveSequence.Count; i++) { switch (moveSequence [i]) { case 'U': player.GetComponent <Player> ().goUp(); break; case 'D': player.GetComponent <Player> ().goDown(); break; case 'L': player.GetComponent <Player> ().goLeft(); break; case 'R': player.GetComponent <Player> ().goRight(); break; } UserInterface.getInstance().setActualTileCost(HyruleMapManager.getInstance().getCostForTile((int)player.transform.position.x, (int)player.transform.position.y)); yield return(new WaitForSeconds(0.1f)); } //yield return new WaitForSeconds (1.0f); movements.Clear(); string objective = HyruleMapManager.getInstance().reachedObjectiveAction((int)player.transform.position.x, (int)player.transform.position.y); if (objective == "pendant0" || objective == "pendant1" || objective == "pendant2") { player.GetComponent <Player>().gotPendant(); } agentBusy = false; saveHyrule(); }
/* * Aqui tem o inicio o Algoritmo de Busca Heurística A* * * Algoritmo: * * calcula os valores de heurística no mapa para o objetivo * marca todos as posições do mapa como não visitadas * * coloca a posição inicial do agente na fila * escolhe para expandir o nodo com menor valor de f(n) = g(n) + h(n) * expande o nodo, adicionando os resultados na fila * repete essas ações até encontrar um nodo que corresponde ao objetivo * */ private IEnumerator astar_search(Vector2 goal) { //Debug.Log ("goal: " + goal); // Calculando as Heurísticas no Mapa HyruleMapManager.getInstance().setHeuristicBoard((int)goal.x, (int)goal.y); // Marcando todas as posições do mapa como não visitadas HyruleMapManager.getInstance().unvisit(); //Debug.Log("player current position: " + player.transform.position); // Caso haja alguma posição resquiciosa de outras execuções limpa a fila if (evaluationQueue.Count > 0) { evaluationQueue.Clear(); } // Colocando posição atual na fila de avaliação evaluationQueue.Add(new Tile(player.transform.position, 0, 0, null)); Tile evaluating; // Escolhe o nodo a ser expandido de menor valor f(n) // Remove-o da fila de nodos a serem avaliados e adiciona na lista de nodos avaliados evaluating = evaluationQueue [0]; for (int i = 1; i < evaluationQueue.Count; i++) { if (evaluating.getF() > evaluationQueue [i].getF()) { evaluating = evaluationQueue [i]; } } evaluationQueue.Remove(evaluating); //Debug.Log("evaluating position " + evaluating.getPosition()); closeEvaluations.Add(evaluating); // Marca na interface gráfica que o nodo foi expandido #if VISUALIZE_ASTAR_STEPS HyruleMapManager.getInstance().markClosed((int)evaluating.getPosition().x, (int)evaluating.getPosition().y); #endif // Faz a expansão do nodo expandNode(evaluating); #if VISUALIZE_ASTAR_STEPS yield return(new WaitForSeconds(delayTime)); #endif // Prepara o próximo nodo para ser avaliado pelo mesmo próximo feito anteriormente evaluating = evaluationQueue [0]; for (int i = 1; i < evaluationQueue.Count; i++) { if (evaluating.getF() > evaluationQueue [i].getF()) { evaluating = evaluationQueue [i]; } } evaluationQueue.Remove(evaluating); closeEvaluations.Add(evaluating); #if VISUALIZE_ASTAR_STEPS HyruleMapManager.getInstance().markClosed((int)evaluating.getPosition().x, (int)evaluating.getPosition().y); #endif // Executa essas ações já descritas acima enquanto houverem elementos a serem avaliados while (evaluationQueue.Count > 0) { expandNode(evaluating); #if VISUALIZE_ASTAR_STEPS yield return(new WaitForSeconds(delayTime)); #endif // Caso o nodo atual seja o objetivo if (evaluating.getPosition() == goal) { // São calculados os movimentos gerados pelo algoritmo insertMovement(evaluating); // O Agente então se movimenta StartCoroutine(move_to(movements)); #if VISUALIZE_ASTAR_STEPS yield break; #else yield return(new WaitForSeconds(delayTime)); #endif } evaluating = evaluationQueue [0]; for (int i = 1; i < evaluationQueue.Count; i++) { if (evaluating.getF() > evaluationQueue [i].getF()) { evaluating = evaluationQueue [i]; } } evaluationQueue.Remove(evaluating); closeEvaluations.Add(evaluating); #if VISUALIZE_ASTAR_STEPS HyruleMapManager.getInstance().markClosed((int)evaluating.getPosition().x, (int)evaluating.getPosition().y); #endif } }
/* * * Função para expandir nodo, recebe um nodo. * * A função recebe um nodo e coloca na fila os 4 nodos adjacentes, cada um com * seus custos de g(n) e f(n) * * Nessa função, os nodos expandidos são marcados como visitados e são mostrados na interface * na lista de proximos a serem expandidos * */ private void expandNode(Tile node) { //Debug.Log ("expanding node"); Vector2 pos; int h; int g_value; // obtendo a posição do nodo resultado da expansão para cima pos = node.getPosition() + Vector2.up; // obtendo o valor de g(n) para esse nodo g_value = node.getG() + (HyruleMapManager.getInstance().getCostForTile((int)pos.x, (int)pos.y)); // calculando o valor de h(n) h = (g_value + (HyruleMapManager.getInstance().getHeuristicValue((int)pos.x, (int)pos.y))); // Verificando se o nodo é possível de ser visitado (h >= 0) e se ele não foi visitado if (h >= 0 && HyruleMapManager.getInstance().visit((int)pos.x, (int)pos.y)) { this.insertOnQueueList(new Tile(pos, h, g_value, node)); // Marca na interface gráfica que o nodo foi aberto para expansão #if VISUALIZE_ASTAR_STEPS HyruleMapManager.getInstance().markOpen((int)pos.x, (int)pos.y); #endif } // obtendo a posição do nodo resultado da expansão para baixo pos = node.getPosition() + Vector2.down; g_value = node.getG() + (HyruleMapManager.getInstance().getCostForTile((int)pos.x, (int)pos.y)); h = (g_value + (HyruleMapManager.getInstance().getHeuristicValue((int)pos.x, (int)pos.y))); if (h >= 0 && HyruleMapManager.getInstance().visit((int)pos.x, (int)pos.y)) { this.insertOnQueueList(new Tile(pos, h, g_value, node)); #if VISUALIZE_ASTAR_STEPS HyruleMapManager.getInstance().markOpen((int)pos.x, (int)pos.y); #endif } // obtendo a posição do nodo resultado da expansão para a esquerda pos = node.getPosition() + Vector2.left; g_value = node.getG() + (HyruleMapManager.getInstance().getCostForTile((int)pos.x, (int)pos.y)); h = (g_value + (HyruleMapManager.getInstance().getHeuristicValue((int)pos.x, (int)pos.y))); if (h >= 0 && HyruleMapManager.getInstance().visit((int)pos.x, (int)pos.y)) { this.insertOnQueueList(new Tile(pos, h, g_value, node)); #if VISUALIZE_ASTAR_STEPS HyruleMapManager.getInstance().markOpen((int)pos.x, (int)pos.y); #endif } // obtendo a posição do nodo resultado da expansão para a direita pos = node.getPosition() + Vector2.right; g_value = node.getG() + (HyruleMapManager.getInstance().getCostForTile((int)pos.x, (int)pos.y)); h = (g_value + (HyruleMapManager.getInstance().getHeuristicValue((int)pos.x, (int)pos.y))); if (h >= 0 && HyruleMapManager.getInstance().visit((int)pos.x, (int)pos.y)) { this.insertOnQueueList(new Tile(pos, h, g_value, node)); #if VISUALIZE_ASTAR_STEPS HyruleMapManager.getInstance().markOpen((int)pos.x, (int)pos.y); #endif } }
/* * Função Awake() pertence ao escopo de funções do Unity * A função Awake é executada antes da função Start() */ void Awake() { // Referentes ao Padrão Singleton if (instance == null) { instance = this; } else if (instance != this) { Destroy(gameObject); } DontDestroyOnLoad(this); hyruleMap.createHeuristicBoard(); dungeon1Map.createHeuristicBoard(); dungeon2Map.createHeuristicBoard(); dungeon3Map.createHeuristicBoard(); map.createHeuristicBoard(); // Criando os vetores de referências dungeonsReference = new GameObject[dungeons.Length]; pendantsReference = new GameObject[pendants.Length]; dungeonsExitReference = new GameObject[dungeonsExitPosition.Length]; // Informando que nenhum pingente ainda foi coletado pendantsTaken = new bool[pendantsPositions.Length]; for (int i = 0; i < pendantsTaken.Length; i++) { pendantsTaken [i] = false; } // Link deve voltar à sua casa antes de partir? //GameManager.getInstance ().push (new Vector2(playerStartingPosition.y - map.getCount() / 2, playerStartingPosition.x - map.getCount() / 2); /* * * TO DO * * Para encontrar a melhor ordem para pegar os pingentes é necessário * resolver o problema do caixeiro viajante (travelling salesman). * * Para isto considere que antes do agente chegar à entrada de Lost Woods ele deverá iniciar a * jornada na casa de Link e voltar a esta casa com todos os pingentes coletados. * * Resolva tal problema usando alguma das técnicas de busca especificadas na disciplina. * * O caixeiro viajante é feito na posição das dungeons, a posição das dungeons são então empilhadas * na pilha de objetivos */ //GameManager.getInstance ().push (new Vector2(dungeonPositions [1].y - (map.getCount()/2), dungeonPositions[1].x - (map.getCount()/2))); //GameManager.getInstance ().push (new Vector2(dungeonPositions [2].y - (map.getCount()/2), dungeonPositions[2].x - (map.getCount()/2))); //GameManager.getInstance ().push (new Vector2(dungeonPositions [0].y - (map.getCount()/2), dungeonPositions[0].x - (map.getCount()/2))); // Criando os Holders dos tiles referentes ao A* openTilesHolder = new GameObject("Open Tiles Holder"); closedTilesHolder = new GameObject("Closed Tiles Holder"); pathTilesHolder = new GameObject("Path Tiles Holder"); // Instanciando o Agente e guardando sua referência playerRef = Instantiate(player, new Vector3(playerStartingPosition.y - (map.getCount() / 2), playerStartingPosition.x - (map.getCount() / 2), -9), Quaternion.identity) as GameObject; playerRef.transform.eulerAngles = new Vector3(playerRef.transform.eulerAngles.x, playerRef.transform.eulerAngles.y, playerRef.transform.eulerAngles.z + 90); }