// sokoban_push_with_feasibility_fix_aXX_final.cpp
// C++14
// Giữ nguyên logic gốc, thêm: nhiều goal A + bỏ qua hộp kề mà ngay sau nó có 'X' (không phải A)
#include <bits/stdc++.h>
using namespace std;
struct Pos {
int r, c;
bool operator==(const Pos &o) const { return r == o.r && c == o.c; }
bool operator!=(const Pos &o) const { return !(*this == o); }
};
// For using pair<Pos,Pos> in unordered containers
struct HashState {
size_t operator()(const pair<Pos, Pos> &p) const {
return (p.first.r * 31 + p.first.c) * 131 + (p.second.r * 31 + p.second.c);
}
};
int R = 16, C = 16;
vector<string> grid;
Pos myPos;
vector<Pos> goalsA;
Pos goalB = {-1,-1};
vector<vector<bool>> isDeadCorner;
// Directions: Up, Down, Left, Right
int dr[4] = {-1, 1, 0, 0};
int dc[4] = {0, 0, -1, 1};
char moveChar[4] = {'U', 'D', 'L', 'R'};
bool inBounds(int r, int c) {
return r >= 0 && r < R && c >= 0 && c < C;
}
// Hard wall: only real wall '#' or outside bounds
bool isHardWall(int r, int c) {
return !inBounds(r, c) || grid[r][c] == '#';
}
// Walk-block: used for normal walking (treat 'b' as block for walking)
bool isWalkBlocked(int r, int c) {
return !inBounds(r, c) || grid[r][c] == '#' || grid[r][c] == 'b';
}
int manhattan(const Pos &a, const Pos &b) {
return abs(a.r - b.r) + abs(a.c - b.c);
}
bool isGoalA(const Pos &p) {
for (size_t i = 0; i < goalsA.size(); ++i) if (goalsA[i] == p) return true;
return false;
}
Pos nearestGoalA(const Pos &b) {
if (goalsA.empty()) return Pos{-1,-1};
Pos best = goalsA[0];
int bd = manhattan(b, best);
for (size_t i = 1; i < goalsA.size(); ++i) {
int d = manhattan(b, goalsA[i]);
if (d < bd) { bd = d; best = goalsA[i]; }
}
return best;
}
// Dead corner precompute: nếu 1 ô trống kề 2 bức tường theo góc vuông (không tính goalA)
void computeDeadCorners() {
isDeadCorner.assign(R, vector<bool>(C, false));
for (int r = 0; r < R; r++) {
for (int c = 0; c < C; c++) {
if (!inBounds(r,c)) continue;
if (grid[r][c] == '#') continue;
if (isGoalA(Pos{r,c})) continue;
bool up = isHardWall(r - 1, c), down = isHardWall(r + 1, c);
bool left = isHardWall(r, c - 1), right = isHardWall(r, c + 1);
if ((up && left) || (up && right) || (down && left) || (down && right)) {
isDeadCorner[r][c] = true;
}
}
}
}
// ------------------ helper canReach ------------------
// BFS to see if player can reach targetPos without moving box
// Respects isWalkBlocked() and treats the specified box cell as blocked.
bool canReach(const Pos &start, const Pos &target, const Pos &box) {
if (!inBounds(start.r, start.c) || !inBounds(target.r, target.c)) return false;
vector<vector<char>> vis(R, vector<char>(C, 0));
queue<Pos> q;
vis[start.r][start.c] = 1;
q.push(start);
while (!q.empty()) {
Pos cur = q.front(); q.pop();
if (cur == target) return true;
for (int d = 0; d < 4; ++d) {
int nr = cur.r + dr[d], nc = cur.c + dc[d];
if (!inBounds(nr,nc)) continue;
if (vis[nr][nc]) continue;
if (isWalkBlocked(nr,nc)) continue;
// cannot step onto the box cell
if (nr == box.r && nc == box.c) continue;
vis[nr][nc] = 1;
q.push({nr,nc});
}
}
return false;
}
// ------------------ boxCanReachGoal ------------------
// BFS on push-states (boxPos, playerPos) to see whether the box can be pushed to any goalA
// Rules used:
// - Player moves must avoid walls and cannot step into box cell.
// - A push from box -> ahead is valid if ahead is not a hard wall, not another box (unless that cell is goalA), not goalB, not dead-corner (unless goalA).
bool boxCanReachGoal(const Pos &boxStart, const Pos &playerStart) {
if (isGoalA(boxStart)) return true;
unordered_set<pair<Pos, Pos>, HashState> vis;
queue<pair<Pos,Pos>> q;
vis.insert({boxStart, playerStart});
q.push({boxStart, playerStart});
while (!q.empty()) {
pair<Pos,Pos> st = q.front(); q.pop();
Pos b = st.first;
Pos p = st.second;
for (int d = 0; d < 4; ++d) {
int behind_r = b.r - dr[d], behind_c = b.c - dc[d]; // where player must stand
int ahead_r = b.r + dr[d], ahead_c = b.c + dc[d]; // box will move here
// check ahead
if (!inBounds(ahead_r, ahead_c)) continue;
if (isHardWall(ahead_r, ahead_c)) continue;
if (grid[ahead_r][ahead_c] == 'X' && !isGoalA(Pos{ahead_r,ahead_c})) continue;
if (Pos{ahead_r,ahead_c} == goalB) continue;
if (isDeadCorner[ahead_r][ahead_c] && !isGoalA(Pos{ahead_r,ahead_c})) continue;
// behind must be valid
if (!inBounds(behind_r, behind_c)) continue;
if (isWalkBlocked(behind_r, behind_c)) continue;
if (grid[behind_r][behind_c] == 'X' && !(behind_r==b.r && behind_c==b.c)) continue;
// player must reach behind without moving b
if (!canReach(p, Pos{behind_r, behind_c}, b)) continue;
Pos newBox = Pos{ahead_r, ahead_c};
Pos newPlayer = b;
if (isGoalA(newBox)) return true;
pair<Pos,Pos> newState = {newBox, newPlayer};
if (!vis.count(newState)) {
vis.insert(newState);
q.push(newState);
}
}
}
return false;
}
// -------------------- A* (find path to nearest goalA for this box) --------------------
struct State2 {
Pos player, box;
int g, h;
bool operator>(const State2 &o) const { return g + h > o.g + o.h; }
};
// A*: tìm đường đưa box startBox đến targetGoal (nearestGoalA(startBox))
// Tránh sửa logic gốc về các ràng buộc push/walk
string aStarBox(Pos startPlayer, Pos startBox) {
Pos targetGoal = nearestGoalA(startBox);
if (targetGoal.r == -1) return "";
priority_queue<State2, vector<State2>, greater<State2>> pq;
unordered_map<pair<Pos, Pos>, pair<Pos, Pos>, HashState> parent;
unordered_map<pair<Pos, Pos>, char, HashState> moveTaken;
unordered_map<pair<Pos, Pos>, int, HashState> bestDistToGoal;
auto boxDist = [&](const Pos &b)->int {
return manhattan(b, targetGoal);
};
auto heuristic = [&](const Pos &player, const Pos &box)->int {
return boxDist(box) * 3 + manhattan(player, box);
};
pq.push({startPlayer, startBox, 0, heuristic(startPlayer, startBox)});
bestDistToGoal[{startPlayer, startBox}] = boxDist(startBox);
while (!pq.empty()) {
State2 cur = pq.top(); pq.pop();
// goal: box on any goalA
if (isGoalA(cur.box)) {
string path;
pair<Pos, Pos> key = {cur.player, cur.box};
while (parent.count(key)) {
path.push_back(moveTaken[key]);
key = parent[key];
}
reverse(path.begin(), path.end());
return path;
}
// safety depth limit
if (cur.g > 200) continue;
for (int k = 0; k < 4; ++k) {
Pos newPlayer = {cur.player.r + dr[k], cur.player.c + dc[k]};
// WALK case
if (!(newPlayer == cur.box)) {
if (isWalkBlocked(newPlayer.r, newPlayer.c)) continue;
pair<Pos,Pos> key = {newPlayer, cur.box};
int ndist = boxDist(cur.box);
if (!bestDistToGoal.count(key) || ndist < bestDistToGoal[key]) {
bestDistToGoal[key] = ndist;
parent[key] = {cur.player, cur.box};
moveTaken[key] = moveChar[k];
pq.push({newPlayer, cur.box, cur.g + 1, heuristic(newPlayer, cur.box)});
}
}
// PUSH case
else {
Pos newBox = {cur.box.r + dr[k], cur.box.c + dc[k]};
// 1) newBox must be in bounds and not a hard wall ('#')
if (!inBounds(newBox.r, newBox.c)) continue;
if (isHardWall(newBox.r, newBox.c)) continue;
// 2) disallow pushing into 'b'
if (grid[newBox.r][newBox.c] == 'b') continue;
// 3) disallow pushing into another box (unless that cell is goalA)
if (grid[newBox.r][newBox.c] == 'X' && !isGoalA(newBox)) continue;
// 4) disallow pushing into dead corner (unless it's goalA)
if (isDeadCorner[newBox.r][newBox.c] && !isGoalA(newBox)) continue;
// 5) disallow pushing into opponent goal B
if (newBox == goalB) continue;
pair<Pos,Pos> key = {newPlayer, newBox};
int ndist = boxDist(newBox);
if (!bestDistToGoal.count(key) || ndist < bestDistToGoal[key]) {
bestDistToGoal[key] = ndist;
parent[key] = {cur.player, cur.box};
moveTaken[key] = moveChar[k];
pq.push({newPlayer, newBox, cur.g + 1, heuristic(newPlayer, newBox)});
}
}
}
}
return "";
}
// -------------------- main (selection uses feasibility + aXX guard) --------------------
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// Read grid (R x C assumed 16x16 by default)
grid.resize(R);
for (int i = 0; i < R; i++) {
cin >> grid[i];
for (int j = 0; j < C; j++) {
if (grid[i][j] == 'a') myPos = {i, j};
if (grid[i][j] == 'A') goalsA.push_back({i, j});
if (grid[i][j] == 'B') goalB = {i, j};
}
}
computeDeadCorners();
// Gather boxes
vector<Pos> boxes;
for (int r = 0; r < R; r++)
for (int c = 0; c < C; c++)
if (grid[r][c] == 'X') boxes.push_back({r, c});
// If no boxes -> stay
if (boxes.empty()) {
cout << "S\n";
return 0;
}
string bestPath;
int bestDist = INT_MAX;
// Scan boxes (prefer nearer ones)
for (size_t i = 0; i < boxes.size(); ++i) {
Pos box = boxes[i];
// skip dead corner boxes (unless the box is on some A)
if (isDeadCorner[box.r][box.c] && !isGoalA(box)) continue;
int distToBox = manhattan(myPos, box);
if (distToBox > 50) continue; // optional prune for very far boxes
// --- FIX for aXX:
// If player is adjacent to this box (dist == 1) and the cell beyond the box in that direction
// is an 'X' that is NOT a goalA, then immediate push from player would hit another box -> skip this box.
if (distToBox == 1) {
int dirR = box.r - myPos.r;
int dirC = box.c - myPos.c;
int dir = -1;
for (int k = 0; k < 4; ++k) {
if (dr[k] == dirR && dc[k] == dirC) { dir = k; break; }
}
if (dir != -1) {
int beyond_r = box.r + dr[dir], beyond_c = box.c + dc[dir];
if (inBounds(beyond_r, beyond_c) && grid[beyond_r][beyond_c] == 'X' && !isGoalA(Pos{beyond_r,beyond_c})) {
// skip this box — it's the aXX pattern (player adjacent but pushing that way hits another X)
continue;
}
}
}
// Feasibility check — only consider this box if it can be pushed to some goal A
if (!boxCanReachGoal(box, myPos)) continue;
string path = aStarBox(myPos, box);
if (!path.empty() && distToBox < bestDist) {
bestDist = distToBox;
bestPath = path;
}
}
if (!bestPath.empty()) {
cout << bestPath[0] << "\n";
return 0;
}
// fallback: random legal move (respecting isWalkBlocked)
vector<int> dirs = {0,1,2,3};
random_shuffle(dirs.begin(), dirs.end());
for (int k : dirs) {
int nr = myPos.r + dr[k], nc = myPos.c + dc[k];
if (!inBounds(nr,nc)) continue;
if (isWalkBlocked(nr,nc)) continue;
if (grid[nr][nc] == 'X') continue; // don't step onto other box
cout << moveChar[k] << "\n";
return 0;
}
cout << "S\n";
return 0;
}