이 책에서 '게임 개발' 이라는 문제에 대해서 구현 해보려고 합니다.
문제를 간략히 설명해 보면, N x M의 게임 맵의 대한 정보가 주어지고, 1은 바다, 0은 육지로 되어 있는 게임 맵에서 게임 캐릭터의 위치 (x, y) 정보가 주어지고, 캐릭터가 바라보고 있는 방향(북-0/동-1/남-2/서-3) 정보가 주어졌을 때, 게임 메뉴얼 대로 동작 한 후, 캐릭터가 이동한 최소 칸 수를 구하라는 문제 입니다.
1. 먼저
메뉴얼(*메뉴얼의 내용은 책을 참조)을 바탕으로 CharactorProcessor 클래스의 메서드들을 정의 했습니다.
1. 현재 캐릭터를 기준으로 왼쪽 방향으로 이동 할수 있는지?
=> boolean isGoLeft()
2. 캐릭터의 방향을 왼쪽(반시계 방향)으로 돌린다.
=> void rotateLeft()
3. 캐릭터가 앞으로 전진 한다.
=> void goStright()
4. 캐릭터 뒤쪽이 바다 이니?
=> boolean isTheSeaBEhind()
5. 캐릭터 뒤쪽으로 후진 한다.
=> void goBack()
6. 캐릭터가 멈춘다.
=> void stop()
7. 캐릭터가 메뉴얼 대로 움직인다.
=> void run()
2. Charactor 클래스 정의, 동/서/남/북 상수값을 표현할 CARDINAL_POINTS Enum 정의.
class Charactor{
private int x;
private int y;
private CARDINAL_POINTS facePostion;
public Charactor(int x, int y, CARDINAL_POINTS facePostion) {
this.x = x;
this.y = y;
this.facePostion = facePostion;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public CARDINAL_POINTS getFacePostion() {
return facePostion;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setFacePostion(CARDINAL_POINTS facePostion) {
this.facePostion = facePostion;
}
}
enum CARDINAL_POINTS {
NORTH,
WEST,
SOUTH,
EAST;
public static final int length = CARDINAL_POINTS.values().length;
}
3. CharactorProcessor의 메서드를 바탕으로, void run() 메서드를 구현.
// 캐릭터 로직
public void run(){
// 동,서,남,북 이동(4)
for (int i = 0; i < CARDINAL_POINTS.length; i++) {
if(isGoLeft()) {
rotateLeft();
goStright();
run();
return;
} else {
rotateLeft();
}
}
// 동,서,남,북 모두 이동 할 곳 없다면
// 뒤로 가거나, 갈수 없다면 멈춘다
if(isTheSeaBehind()) {
stop();
} else {
goBack();
run();
}
}
3. 게임 캐릭터의 로직을 담당 하는 CharactorProcessor 클래스를 구현.
public class Page118 {
// 입력 예시
// 4 4 => 4 x 4 맵 생성
// 1 1 0 => (1, 1)에 북쪽(0)을 바라보고 서 있는 캐릭터
// 1 1 1 1 => 바다/바다/바다/바다
// 1 0 0 1 => 바다/육지/육지/바다
// 1 1 0 1 => 바다/바다/육지/바다
// 1 1 1 1 => 바다/바다/바다/바다
// 출력 예시
// 3
public static void main(String[] args) {
System.out.print(new Page118().solution());
}
int solution(){
int N = 4, M = 4;
int[][] gameMap = {
{1, 1, 1, 1},
{1, 0, 0, 1},
{1, 1, 0, 1},
{1, 1, 1, 1}};
CharactorProcessor processor = new CharactorProcessor(
new Charactor(1, 1, CARDINAL_POINTS.NORTH), gameMap, N, M);
processor.run();
return processor.getMiniumDistance();
}
}
class CharactorProcessor{
private Charactor charactor;
private final int[][] GAME_MAP;
private int[][] visitedMap;
private final int SEA;
private final int VISITED;
private final int N;
private final int M;
private int miniMumDistance;
public CharactorProcessor(Charactor charactor, int[][] gameMap, int N, int M) {
this.charactor = charactor;
this.GAME_MAP = gameMap;
this.visitedMap = gameMap;
this.SEA = 1;
this.VISITED = 1;
this.N = N;
this.M = M;
this.miniMumDistance = 0;
// 현재 위치 방문 체크
checkVisitedPosition(charactor.getX(), charactor.getY());
}
// 최소 이동 칸 반환
public int getMiniumDistance(){
return miniMumDistance;
}
// 캐릭터 로직
public void run(){
// 동,서,남,북 이동(4)
for (int i = 0; i < CARDINAL_POINTS.length; i++) {
if(isGoLeft()) {
rotateLeft();
goStright();
run();
return;
} else {
rotateLeft();
}
}
// 동,서,남,북 모두 이동 할 곳 없다면
// 뒤로 가거나, 갈수 없다면 멈춘다
if(isTheSeaBehind()) {
stop();
} else {
goBack();
run();
}
}
// 왼쪽 방향의 갈곳이 있으면 true
// 북-0/동-1/남-2/서-3
public boolean isGoLeft(){
// 이동할 방향(동/서/남/북)
CARDINAL_POINTS toGoFace = getFacePositionToGo();
// 이동할 위치(x, y)
int[] postion = getMapPositionToGo(toGoFace);
// 갈방향이 바다면 false 반환
// 이미 방문한 노드라면 false 반환
// 맵이 아니라면 false 반환
return isInMap(postion)
&& !isASea(postion)
&& !isVisited(postion);
}
// 방문 했으면 true
private boolean isVisited(int[] postion){
return visitedMap[postion[0]][postion[1]] == VISITED;
}
// 바다면 true
private boolean isASea(int[] postion) {
return GAME_MAP[postion[0]][postion[1]] == SEA;
}
// 맵 안이면 true
private boolean isInMap(int[] postion) {
return postion[0] >= 0 && postion[0] < N
&& postion[1] >= 0 && postion[1] < M;
}
// 왼쪽으로 회전
public void rotateLeft(){
charactor.setFacePostion(getFacePositionToGo());
System.out.print("rotateLeft");
System.out.println();
}
// 앞으로 전진
public void goStright(){
int[] mapPostionToGo = getMapPositionToGo(charactor.getFacePostion());
charactor.setX(mapPostionToGo[0]);
charactor.setY(mapPostionToGo[1]);
//이동 한 위치 방문 표시
checkVisitedPosition(mapPostionToGo[0], mapPostionToGo[1]);
System.out.print("goStright");
System.out.println();
}
// 갈곳이 없을 경우, 바라보는 곳을 유지하고
// 한칸 뒤로 이동
public void goBack(){
int[] mapPosition = getMapPositionToBack();
charactor.setX(mapPosition[0]);
charactor.setY(mapPosition[1]);
//이동 한 위치 방문 표시
checkVisitedPosition(mapPosition[0], mapPosition[1]);
System.out.print("goBack");
System.out.println();
}
// 뒤칸이 바다 이면 true
private boolean isTheSeaBehind(){
int[] mapPosition = getMapPositionToBack();
// 맵 안 이면서,
// 바다 이면 true
return isInMap(mapPosition)
&& isASea(mapPosition);
}
// 모두 갈곳이 없다면, 멈춘다.
public void stop(){
System.out.print("stop");
System.out.println();
}
private CARDINAL_POINTS getFacePositionToGo() {
CARDINAL_POINTS toGoFace = null;
if (CARDINAL_POINTS.NORTH == charactor.getFacePostion()) {
toGoFace = CARDINAL_POINTS.WEST;
} else if (CARDINAL_POINTS.WEST == charactor.getFacePostion()) {
toGoFace = CARDINAL_POINTS.SOUTH;
} else if (CARDINAL_POINTS.SOUTH == charactor.getFacePostion()) {
toGoFace = CARDINAL_POINTS.EAST;
} else if (CARDINAL_POINTS.EAST == charactor.getFacePostion()) {
toGoFace = CARDINAL_POINTS.NORTH;
}
return toGoFace;
}
//동 으로 가려면 y + 1
//북 로 가려면 x - 1
//서 로 가려면 y - 1
//남 으로 가려면 x + 1
//가야할 방향을 파라미터로 주면 이동할 map 위치를 반환
private int[] getMapPositionToGo(CARDINAL_POINTS face) {
int x = charactor.getX();
int y = charactor.getY();
// 북쪽을 바라 보고 있다면, x - 1
if(face == CARDINAL_POINTS.NORTH){
x = charactor.getX() - 1;
// 서쪽을 바라 보고 있다면, y - 1
} else if(face == CARDINAL_POINTS.WEST){
y = charactor.getY() - 1;
// 동쪽을 바라 보고 있다면, y + 1
} else if(face == CARDINAL_POINTS.EAST){
y = charactor.getY() + 1;
// 남쪽을 바라 보고 있다면, x + 1
} else if(face == CARDINAL_POINTS.SOUTH){
x = charactor.getX() + 1;
}
return new int[]{x, y};
}
private int[] getMapPositionToBack() {
int x = charactor.getX();
int y = charactor.getY();
// 북쪽을 바라 보고 있다면, x + 1
if (CARDINAL_POINTS.NORTH == charactor.getFacePostion()) {
x = charactor.getX() + 1;
// 서쪽을 바라 보고 있다면, y + 1
} else if (CARDINAL_POINTS.WEST == charactor.getFacePostion()){
y = charactor.getY() + 1;
// 남쪽을 바라 보고 있다면, x - 1
} else if (CARDINAL_POINTS.SOUTH == charactor.getFacePostion()){
x = charactor.getX() - 1;
// 동쪽을 바라 보고 있다면, y + 1
} else if (CARDINAL_POINTS.EAST == charactor.getFacePostion()) {
y = charactor.getY() + 1;
}
return new int[]{x, y};
}
private void checkVisitedPosition(int x, int y) {
//현재 위치 방문 표시
visitedMap[x][y] = 1;
//이동 칸 증가
miniMumDistance++;
}
}
class Charactor{
private int x;
private int y;
private CARDINAL_POINTS facePostion;
public Charactor(int x, int y, CARDINAL_POINTS facePostion) {
this.x = x;
this.y = y;
this.facePostion = facePostion;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public CARDINAL_POINTS getFacePostion() {
return facePostion;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setFacePostion(CARDINAL_POINTS facePostion) {
this.facePostion = facePostion;
}
}
enum CARDINAL_POINTS {
NORTH,
WEST,
SOUTH,
EAST;
public static final int length = CARDINAL_POINTS.values().length;
}
이렇게 구현 하고, 책에서 제시하는 문제 해설과 소스 코드를 확인 했습니다. 확인 하고서 제소스를 좀더 변경 할수 있는 부분을 확인 할 수 있었습니다. BFS 알고리즘에서도 많이 사용하는 현재 위치에서 위/아래/왼쪽/오른쪽 == 동/서/남/북 의 위치 값을 구할때, 책의 저자 나동빈님은 주로 dx={-1, 1, 0, 0}, dy={0, 0, -1, 1} 을 정의 하여 현재 좌표값을 더하여 다음으로 이동할 좌표값을 구하였습니다. nx = x + dx[i], ny = y + dy[i] 이 로직을 정의 하여 로직을 더 간단하게 수정할 여지가 보였습니다.
따라서, 원래 아래 처럼 작성 했던 코드를,
private int[] getMapPositionToGo(CARDINAL_POINTS face) {
int x = charactor.getX();
int y = charactor.getY();
// 북쪽을 바라 보고 있다면, x - 1
if(face == CARDINAL_POINTS.NORTH){
x = charactor.getX() - 1;
// 서쪽을 바라 보고 있다면, y - 1
} else if(face == CARDINAL_POINTS.WEST){
y = charactor.getY() - 1;
// 동쪽을 바라 보고 있다면, y + 1
} else if(face == CARDINAL_POINTS.EAST){
y = charactor.getY() + 1;
// 남쪽을 바라 보고 있다면, x + 1
} else if(face == CARDINAL_POINTS.SOUTH){
x = charactor.getX() + 1;
}
return new int[]{x, y};
}
private int[] getMapPositionToBack() {
int x = charactor.getX();
int y = charactor.getY();
// 북쪽을 바라 보고 있다면, x + 1
if (CARDINAL_POINTS.NORTH == charactor.getFacePostion()) {
x = charactor.getX() + 1;
// 서쪽을 바라 보고 있다면, y + 1
} else if (CARDINAL_POINTS.WEST == charactor.getFacePostion()){
y = charactor.getY() + 1;
// 남쪽을 바라 보고 있다면, x - 1
} else if (CARDINAL_POINTS.SOUTH == charactor.getFacePostion()){
x = charactor.getX() - 1;
// 동쪽을 바라 보고 있다면, y + 1
} else if (CARDINAL_POINTS.EAST == charactor.getFacePostion()) {
y = charactor.getY() + 1;
}
return new int[]{x, y};
}
아래와 같이 수정 했습니다.
this.dx = new HashMap<>();
this.dy = new HashMap<>();
this.dx.put(CARDINAL_POINTS2.NORTH, 0);
this.dx.put(CARDINAL_POINTS2.SOUTH, 0);
this.dx.put(CARDINAL_POINTS2.EAST, -1);
this.dx.put(CARDINAL_POINTS2.WEST, 1);
this.dy.put(CARDINAL_POINTS2.NORTH, -1);
this.dy.put(CARDINAL_POINTS2.SOUTH, 1);
this.dy.put(CARDINAL_POINTS2.EAST, 0);
this.dy.put(CARDINAL_POINTS2.WEST, 0);
private int[] getMapPositionToGo() {
int nx = charactor.getX() + dx.get(charactor.getFacePosition());
int ny = charactor.getY() + dy.get(charactor.getFacePosition());
return new int[]{nx, ny};
}
private int[] getMapPositionToBack() {
int nx = charactor.getX() - dx.get(charactor.getFacePosition());
int ny = charactor.getY() - dy.get(charactor.getFacePosition());
return new int[]{nx, ny};
}
dx, dy Map을 enum 으로 초기화 시켜야 해서 초기화 로직이 지저분 해지긴 했지만, 메서드는 보기 좋아진거 같습니다.
'Algorithm' 카테고리의 다른 글
개미 전사 - '이것이 코딩 테스트다 with 파이썬' (0) | 2020.08.31 |
---|---|
두 배열의 원소 교체 - '이것이 코딩 테스트다 with 파이썬' (0) | 2020.08.31 |
미로 탈출 문제 (0) | 2020.08.29 |
카카오 가사 검색 문제(Trie 트리) (0) | 2020.08.28 |
이진 탐색(Binary Search) (0) | 2020.08.27 |