상호의 배틀필드
1. 문제 접근 방법 및 아이디어
- 초기 상태 파악
- 2차원 배열을 입력받으며 전차의 초기 위치(
r,c)와 방향(d)을 저장합니다.
- 2차원 배열을 입력받으며 전차의 초기 위치(
- 명령어 해석
- 문자열로 주어진 명령을 순차적으로 읽으며
Move와shoot을 분기 처리합니다.
- 문자열로 주어진 명령을 순차적으로 읽으며
- 이동 로직
- 방향을 먼저 바꾼다.
- 바라보는 앞 칸이 평지(
.)이고 격자 범위 내인지 확인한다. - 조건 만족 시 현재 위치를 평지로 만들고 새로운 위치로 이동한다.
- 포탄 발사 로직
- 전차의 현재 위치에서 현재 방향으로
while루프를 돌며 포탄을 전진시킨다. - 벽(
*,#)을 만나거나 맵 밖으로 나갈 떄까지 반복한다. - 벽의 종류에 따라 파괴 여부를 결정한다.
- 전차의 현재 위치에서 현재 방향으로
- 결과 출력
- 최종 맵 상태를 출력합니다.
2. 필요 개념 및 알고리즘
- 시뮬레이션
- 문제 자체가 특정 알고리즘을 요구하기보다 이 조건대로 움직여라라는 명령형 구조이기 때문입니다.
- 델파 배열
- 상하좌우 이동을 간결하게 처리하는 테크닉입니다.
- 문제에서 상(U), 하(D), 좌(L), 우(R) 4가지 방향이 주어지므로, 이를 인덱스(0~3)로 매핑하여 처리할 수 있습니다.
3. 어려워하는 포인트 또는 실수하는 포인트
- 어려웠던 포인트
- 방향 전환과 이동을 동시에 처리해야 하는 점.
- 이동과 발상에 필요한 아이디어가 떠오르지 못한 점
- 실수하는 포인트
- 이동 실패 시 방향 전화
- 이동을 못 하더라도 전차의 방향은 반드시 바뀌어야 합니다.
- 시뮬레이션 구현 도중 오탈자
- 이동 실패 시 방향 전화
4. 코드 풀이
4.1 초기 전차 정보 파악
static int pr, pc, pd; // 전차의 좌표, 방향
static char tank[] = { '^', 'v', '<', '>' }; // 전차의 모양
// 맵 정보 입력
for (int i = 0; i < H; i++) {
String row = br.readLine();
for (int j = 0; j < W; j++) {
map[i][j] = row.charAt(j);
// 초기 전차 정보 초기화
for (int d = 0; d < 4; d++) {
if (map[i][j] == tank[d]) {
pr = i;
pc = j;
pd = d;
}
}
}
} // 맵 정보 입력 끝
- 초기 전차의 위치와 방향을 탐색합니다.
- 상하좌우 방향으로 표현된 전차의 모양 중 하나를 찾습니다.
4.2 명령어 해석
// 명령어 처리 함수
static void process(char cmd) {
if (cmd == 'S') {
shoot();
} else {
move(cmd);
}
} // 명령어 처리 함수 종료
- shoot와 move를 분리해서 처리합니다.
- shoot가 혼자 독립적이라서 shoot를 먼저 처리합니다.
4.3 방향 설정
// 명령어를 인덱스로 매핑하는 함수
static int getDir(char cmd) {
if(cmd == 'U') return 0;
if(cmd == 'D') return 1;
if(cmd == 'L') return 2;
if(cmd == 'R') return 3;
return -1;
} // 매핑 함수 종료
- U, D, L, R에 해당하는 인덱스를 반환합니다.
4.4 이동 로직
// 이동 함수
static void move(char cmd) {
// 현재 방향
int cd = getDir(cmd);
// 방향 갱신
pd = cd;
map[pr][pc] = tank[cd];
// 다음 좌표
int nr = pr + dr[cd];
int nc = pc + dc[cd];
// 범위에 벗어나면 종료
if (!(0 <= nr && nr < H && 0 <= nc && nc < W)) {
return;
}
// 평지를 발견하면 이동
// 이전에 맵은 평지로 바꾼다.
if (map[nr][nc] == '.') {
map[nr][nc] = tank[cd];
map[pr][pc] = '.';
pr = nr;
pc = nc;
}
} // 이동 함수 종료
- 현재의 방향을 구하고, 방향을 먼저 바꿉니다.
- 바라보는 앞 칸이 평지이고, 격자 범위 내인지 확인합니다.
- 조건 만족 시 현재 위치를 평지로 만들고 새로운 위치로 이동합니다.
4.5 포탄 발사 로직
// 포탄 발사 함수
static void shoot() {
// 현재 좌표
int cr = pr;
int cc = pc;
// 범위에 벗어나거나 벽에 붙이히면 종료
while (true) {
// 다음 좌표
int nr = cr + dr[pd];
int nc = cc + dc[pd];
// 범위에 벗어나면 종료
if (!(0 <= nr && nr < H && 0 <= nc && nc < W)) {
break;
}
// 강철로 만들어진 벽을 만나면 종료
if (map[nr][nc] == '#') {
break;
}
// 벽돌로 만들어진 벽을 만나면 종료
// 벽돌은 파괴되고, 평지가된다.
if (map[nr][nc] == '*') {
map[nr][nc] = '.';
break;
}
// 좌표 갱신
cr = nr;
cc = nc;
}
} // 포탄 발상 함수 종료
- 전차의 현재 위치에서 현재 방향으로 while루프를 돌며 포탄을 전진시킵니다.
- 벽을 만나거나 맵 박으로 나갈 떄까지 반복합니다.
- 벽의 종류(*,#)에 따라 파괴 여부를 결정합니다.
4.6 전체 코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Solution {
static int H, W; // 맵의 높이, 너비
static int N; // 입력의 개수
static int pr, pc, pd; // 전차의 좌표, 방향
static String cmds; // 입력 모음
static char[][] map; // 맵의 정보
static int dr[] = { -1, 1, 0, 0 }; // 행 방향 배열
static int dc[] = { 0, 0, -1, 1 }; // 열 방향 배열
static char tank[] = { '^', 'v', '<', '>' }; // 전차의 모양
public static void main(String[] args) throws NumberFormatException, IOException {
// TODO Auto-generated method stub
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
int T = Integer.parseInt(br.readLine().trim());
// tc수행
for (int tc = 1; tc <= T; tc++) {
// H, W 입력
StringTokenizer st = new StringTokenizer(br.readLine());
H = Integer.parseInt(st.nextToken());
W = Integer.parseInt(st.nextToken());
// 맵 정보 초기화
map = new char[H][W];
// 맵 정보 입력
for (int i = 0; i < H; i++) {
String row = br.readLine();
for (int j = 0; j < W; j++) {
map[i][j] = row.charAt(j);
// 초기 전차 정보 초기화
for (int d = 0; d < 4; d++) {
if (map[i][j] == tank[d]) {
pr = i;
pc = j;
pd = d;
}
}
}
} // 맵 정보 입력 끝
// 입력 개수, 정보 입력
N = Integer.parseInt(br.readLine().trim());
cmds = br.readLine();
// 처리
for (int i = 0; i < N; i++) {
process(cmds.charAt(i));
}
// 출력 내용 추가
sb.append("#" + tc + " ");
for (int i = 0; i < H; i++) {
for (int j = 0; j < W; j++) {
sb.append(map[i][j]);
}
sb.append("\n");
}
} // tc 종료
// 출력
System.out.println(sb);
} // 메인 종료
// 명령어 처리 함수
static void process(char cmd) {
if (cmd == 'S') {
shoot();
} else {
move(cmd);
}
} // 명령어 처리 함수 종료
// 이동 함수
static void move(char cmd) {
int cd = getDir(cmd);
// 방향 갱신
pd = cd;
map[pr][pc] = tank[cd];
// 다음 좌표
int nr = pr + dr[cd];
int nc = pc + dc[cd];
// 범위에 벗어나면 종료
if (!(0 <= nr && nr < H && 0 <= nc && nc < W)) {
return;
}
// 평지를 발견하면 이동
// 이전에 맵은 평지로 바꾼다.
if (map[nr][nc] == '.') {
map[nr][nc] = tank[cd];
map[pr][pc] = '.';
pr = nr;
pc = nc;
}
} // 이동 함수 종료
// 포탄 발사 함수
static void shoot() {
// 현재 좌표
int cr = pr;
int cc = pc;
// 범위에 벗어나거나 벽에 붙이히면 종료
while (true) {
// 다음 좌표
int nr = cr + dr[pd];
int nc = cc + dc[pd];
// 범위에 벗어나면 종료
if (!(0 <= nr && nr < H && 0 <= nc && nc < W)) {
break;
}
// 강철로 만들어진 벽을 만나면 종료
if (map[nr][nc] == '#') {
break;
}
// 벽돌로 만들어진 벽을 만나면 종료
// 벽돌은 파괴되고, 평지가된다.
if (map[nr][nc] == '*') {
map[nr][nc] = '.';
break;
}
// 좌표 갱신
cr = nr;
cc = nc;
}
} // 포탄 발상 함수 종료
// 명령어를 인덱스로 매핑하는 함수
static int getDir(char cmd) {
if(cmd == 'U')
return 0;
if(cmd == 'D')
return 1;
if(cmd == 'L')
return 2;
if(cmd == 'R')
return 3;
return -1;
} // 매핑 함수 종료
}
4.7 개선 버전
4.7.1 초기 전차 초기화 개선
for (int i = 0; i < H; i++) {
String row = br.readLine();
for (int j = 0; j < W; j++) {
map[i][j] = row.charAt(j);
// 전차 위치와 방향 초기화
int d = "^v<>".indexOf(map[i][j]);
if (d != -1) {
pr = i;
pc = j;
pd = d;
}
}
}
indexOf를 활용해서 for문으로 탐색하는 것을 대신했습니다.
4.7.2 getDir 함수 최적화
static int getDir(char cmd) {
// 문자열에서 해당 문자의 위치(index)를 반환하는 기법
return "UDLR".indexOf(cmd);
}
indexOf를 활용해서 if문을 여러 번 쓰는 것을 대신했습니다.