Algorithm

게임 개발 - '이것이 코딩 테스트다 with 파이썬'

태인킴 2020. 8. 31. 17:39
반응형
 

이것이 취업을 위한 코딩 테스트다 with 파이썬

IT 취준생이라면 누구나 입사하고 싶은 카카오ㆍ삼성전자ㆍ네이버ㆍ라인!취업의 성공 열쇠는 알고리즘 인터뷰에 있다!IT 취준생이라면 누구나 가고 싶어 하는 카카오, 라인, 삼성전자의 2016년부

book.naver.com

이 책에서 '게임 개발' 이라는 문제에 대해서 구현 해보려고 합니다. 

문제를 간략히 설명해 보면, 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 으로 초기화 시켜야 해서 초기화 로직이 지저분 해지긴 했지만, 메서드는 보기 좋아진거 같습니다.  

반응형