Skip to content

Commit f61dd38

Browse files
committed
feat: add astar
1 parent 39732b5 commit f61dd38

File tree

3 files changed

+220
-7
lines changed

3 files changed

+220
-7
lines changed

src/main_scene/main_scene.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "../search/bfs/bfs.hpp"
44
#include "../search/ucs/ucs.hpp"
5+
#include "../search/astar/astar.hpp"
56

67
MainScene::MainScene() {}
78
MainScene::~MainScene() {}
@@ -86,12 +87,9 @@ void MainScene::_on_solve_button_pressed() {
8687

8788
if (is_solved = solution.is_solved) {
8889
UtilityFunctions::print("BFS found a solution!");
89-
UtilityFunctions::print("Moves: ", solution.moves.size());
9090
} else {
9191
UtilityFunctions::print("BFS could not find a solution.");
9292
}
93-
UtilityFunctions::print("Time taken: ", solution.duration.count(), " ms");
94-
UtilityFunctions::print("Nodes visited: ", solution.node);
9593
is_searching = false;
9694
break;
9795
}
@@ -101,17 +99,22 @@ void MainScene::_on_solve_button_pressed() {
10199

102100
if (is_solved = solution.is_solved) {
103101
UtilityFunctions::print("UCS found a solution!");
104-
UtilityFunctions::print("Moves: ", solution.moves.size());
105102
} else {
106103
UtilityFunctions::print("UCS could not find a solution.");
107104
}
108-
UtilityFunctions::print("Time taken: ", solution.duration.count(), " ms");
109-
UtilityFunctions::print("Nodes visited: ", solution.node);
110105
is_searching = false;
111106
break;
112107
}
113108
case ASTAR:
114-
UtilityFunctions::print("A* selected");
109+
solution = astar::search_astar(board, pieces);
110+
is_searching = true;
111+
112+
if (is_solved = solution.is_solved) {
113+
UtilityFunctions::print("A* found a solution!");
114+
} else {
115+
UtilityFunctions::print("A* could not find a solution.");
116+
}
117+
is_searching = false;
115118
break;
116119
default:
117120
UtilityFunctions::printerr("Unknown algorithm selected");

src/search/astar/astar.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#include "astar.hpp"
2+
3+
vector<AStarSearchNode> astar::generate_successors_astar(const AStarSearchNode& current_astar_node, const int COST_PER_MOVE) {
4+
vector<AStarSearchNode> successors;
5+
const vector<Piece>& current_pieces = current_astar_node.pieces;
6+
const Board& board_node = current_astar_node.board;
7+
8+
for (size_t piece_idx = 0; piece_idx < current_pieces.size(); ++piece_idx) {
9+
const Piece& piece_to_move = current_pieces[piece_idx];
10+
if (piece_to_move.id == 'K') continue;
11+
12+
for (int direction = -1; direction <= 1; direction += 2) {
13+
for (int steps = 1; ; ++steps) {
14+
vector<Piece> next_pieces_state = current_pieces;
15+
Piece& piece_moved_in_state = next_pieces_state[piece_idx]; // ambil referensi ke piece yang akan digerakkan di state baru
16+
bool move_possible = true;
17+
18+
Coordinates original_coords_of_moving_piece = piece_to_move.coordinates; // simpan koordinat asli sebelum modifikasi
19+
20+
if (piece_to_move.is_vertical) {
21+
for (int s = 1; s <= steps; ++s) {
22+
int y_check;
23+
if (direction > 0) {
24+
y_check = piece_to_move.coordinates.y + piece_to_move.size - 1 + s;
25+
} else {
26+
y_check = piece_to_move.coordinates.y - s;
27+
}
28+
if (!Utils::is_cell_clear(board_node, y_check, piece_to_move.coordinates.x, current_pieces, piece_to_move.id)) {
29+
move_possible = false;
30+
break;
31+
}
32+
}
33+
if (move_possible) {
34+
piece_moved_in_state.coordinates.y += (direction * steps);
35+
}
36+
} else {
37+
for (int s = 1; s <= steps; ++s) {
38+
int x_check;
39+
if (direction > 0) {
40+
x_check = piece_to_move.coordinates.x + piece_to_move.size - 1 + s;
41+
} else {
42+
x_check = piece_to_move.coordinates.x - s;
43+
}
44+
if (!Utils::is_cell_clear(board_node, piece_to_move.coordinates.y, x_check, current_pieces, piece_to_move.id)) {
45+
move_possible = false;
46+
break;
47+
}
48+
}
49+
if (move_possible) {
50+
piece_moved_in_state.coordinates.x += (direction * steps);
51+
}
52+
}
53+
54+
if (!move_possible) {
55+
break;
56+
}
57+
58+
vector<PieceMove> next_path = current_astar_node.path;
59+
PieceMove current_pm;
60+
current_pm.old_coordinates = original_coords_of_moving_piece; // gunakan koordinat asli
61+
current_pm.new_coordinates = piece_moved_in_state.coordinates; // koordinat baru setelah bergerak
62+
next_path.push_back(current_pm);
63+
64+
int g_cost_successor = current_astar_node.g_cost + COST_PER_MOVE; // biaya pergerakan umumnya 1
65+
int h_cost_successor = Utils::calculate(board_node, next_pieces_state);
66+
67+
successors.emplace_back(next_pieces_state, board_node, next_path, g_cost_successor, h_cost_successor, piece_to_move.id, original_coords_of_moving_piece);
68+
}
69+
}
70+
}
71+
return successors;
72+
}
73+
74+
75+
Solution astar::search_astar(const Board& initial_board, const vector<Piece>& initial_pieces) {
76+
auto time_start = chrono::high_resolution_clock::now();
77+
Solution result;
78+
result.is_solved = false;
79+
result.node = 0;
80+
81+
const int COST_PER_MOVE = 1; // biaya setiap gerakan adalah 1
82+
83+
priority_queue<AStarSearchNode, vector<AStarSearchNode>, greater<AStarSearchNode>> pq;
84+
set<string> visited_states;
85+
86+
// node awal
87+
int initial_g_cost = 0;
88+
int initial_h_cost = Utils::calculate(initial_board, initial_pieces);
89+
AStarSearchNode initial_astar_node(initial_pieces, initial_board, {}, initial_g_cost, initial_h_cost);
90+
pq.push(initial_astar_node);
91+
92+
UtilityFunctions::print("ASTAR: Initial g_cost: 0, h_cost: " + godot::String::num_int64(initial_h_cost) + ", f_cost: " + godot::String::num_int64(initial_astar_node.f_cost));
93+
94+
while (!pq.empty()) {
95+
AStarSearchNode current_node = pq.top();
96+
pq.pop();
97+
result.node++;
98+
99+
string current_state_str = current_node.get_state_string();
100+
if (visited_states.count(current_state_str)) {
101+
continue;
102+
}
103+
visited_states.insert(current_state_str);
104+
105+
UtilityFunctions::print("ASTAR: Exploring node. " +
106+
godot::String("f(n):") + godot::String::num_int64(current_node.f_cost) +
107+
godot::String(", g(n):") + godot::String::num_int64(current_node.g_cost) +
108+
godot::String(", h(n):") + godot::String::num_int64(current_node.h_cost) +
109+
". Path length: " + godot::String::num_int64(static_cast<int64_t>(current_node.path.size())));
110+
111+
if (current_node.piece_moved != ' ') {
112+
Coordinates new_pos_for_log = {-1, -1};
113+
// cari piece yang digerakkan di state saat ini untuk mendapatkan posisi barunya
114+
for(const auto& p_state : current_node.pieces) {
115+
if (p_state.id == current_node.piece_moved) {
116+
new_pos_for_log = p_state.coordinates;
117+
break;
118+
}
119+
}
120+
UtilityFunctions::print("Moved piece: ", godot::String::utf8(&current_node.piece_moved, 1), " from (", current_node.original_position.x, ",", current_node.original_position.y, ") to (", new_pos_for_log.x, ",", new_pos_for_log.y, ")");
121+
}
122+
123+
124+
if (Utils::is_exit(current_node.board, current_node.pieces)) {
125+
result.is_solved = true;
126+
result.moves = current_node.path;
127+
UtilityFunctions::print("ASTAR: Solution Found! Optimal steps (g_cost): " + godot::String::num_int64(current_node.g_cost) + ". Total moves: " + godot::String::num_int64(result.moves.size()) + ". Nodes visited: " + godot::String::num_int64(static_cast<int64_t>(result.node)));
128+
break;
129+
}
130+
131+
vector<AStarSearchNode> successors = generate_successors_astar(current_node, COST_PER_MOVE);
132+
133+
for (const auto& successor_node : successors) {
134+
string next_state_str = successor_node.get_state_string();
135+
if (!visited_states.count(next_state_str)) {
136+
pq.push(successor_node);
137+
}
138+
}
139+
}
140+
141+
auto time_end = chrono::high_resolution_clock::now();
142+
result.duration = chrono::duration<double, milli>(time_end - time_start);
143+
144+
if (!result.is_solved) {
145+
UtilityFunctions::print("ASTAR: No solution found. Nodes visited: " + godot::String::num_int64(static_cast<int64_t>(result.node)));
146+
}
147+
return result;
148+
}

src/search/astar/astar.hpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,64 @@
11
#pragma once
22

3+
#include <vector>
4+
#include <queue>
5+
#include <set>
6+
#include <string>
7+
#include <algorithm>
8+
#include <chrono>
9+
10+
#include "../../utils/utils.hpp"
11+
12+
struct AStarSearchNode {
13+
vector<Piece> pieces;
14+
Board board;
15+
vector<PieceMove> path;
16+
int g_cost;
17+
int h_cost;
18+
int f_cost;
19+
20+
char piece_moved;
21+
Coordinates original_position;
22+
23+
AStarSearchNode(const vector<Piece>& p, const Board& b, const vector<PieceMove>& path, int g, int h, char pid = ' ', Coordinates opos = {-1,-1})
24+
: pieces(p), board(b), path(path), g_cost(g), h_cost(h), f_cost(g + h), piece_moved(pid), original_position(opos) {}
25+
26+
// operator untuk priority queue
27+
bool operator>(const AStarSearchNode& other) const {
28+
if (f_cost != other.f_cost) {
29+
return f_cost > other.f_cost;
30+
}
31+
// jika f_cost sama, bisa menggunakan h_cost sebagai tie-breaker
32+
return h_cost > other.h_cost;
33+
}
34+
35+
// fungsi untuk mendapatkan representasi string dari state untuk set 'visited'
36+
string get_state_string() const {
37+
string s = "";
38+
vector<Piece> sorted_pieces = pieces;
39+
40+
sort(sorted_pieces.begin(), sorted_pieces.end(), [](const Piece& a, const Piece& b) {
41+
return a.id < b.id;
42+
});
43+
44+
for (const auto& piece : sorted_pieces) {
45+
if (piece.id == 'K') continue;
46+
s += piece.id;
47+
s += ':';
48+
s += to_string(piece.coordinates.x);
49+
s += ',';
50+
s += to_string(piece.coordinates.y);
51+
s += (piece.is_vertical ? "V" : "H");
52+
s += ";";
53+
}
54+
return s;
55+
}
56+
};
57+
58+
class astar {
59+
public:
60+
static Solution search_astar(const Board& initial_board, const vector<Piece>& initial_pieces);
61+
62+
private:
63+
static vector<AStarSearchNode> generate_successors_astar(const AStarSearchNode& current_node, const int COST_PER_MOVE);
64+
};

0 commit comments

Comments
 (0)