[프로그래머스 2레벨] 리코쳇 로봇

이민선(Jasmine)·2023년 3월 26일
1

문제

리코쳇 로봇이라는 보드게임이 있습니다.

이 보드게임은 격자모양 게임판 위에서 말을 움직이는 게임으로, 시작 위치에서 목표 위치까지 최소 몇 번만에 도달할 수 있는지 말하는 게임입니다.

이 게임에서 말의 움직임은 상, 하, 좌, 우 4방향 중 하나를 선택해서 게임판 위의 장애물이나 맨 끝에 부딪힐 때까지 미끄러져 이동하는 것을 한 번의 이동으로 칩니다.

다음은 보드게임판을 나타낸 예시입니다.

...D..R
.D.G...
....D.D
D....D.
..D....

여기서 "."은 빈 공간을, "R"은 로봇의 처음 위치를, "D"는 장애물의 위치를, "G"는 목표지점을 나타냅니다.
위 예시에서는 "R" 위치에서 아래, 왼쪽, 위, 왼쪽, 아래, 오른쪽, 위 순서로 움직이면 7번 만에 "G" 위치에 멈춰 설 수 있으며, 이것이 최소 움직임 중 하나입니다.

게임판의 상태를 나타내는 문자열 배열 board가 주어졌을 때, 말이 목표위치에 도달하는데 최소 몇 번 이동해야 하는지 return 하는 solution함수를 완성하세요. 만약 목표위치에 도달할 수 없다면 -1을 return 해주세요.

제한 사항

  • 3 ≤ board의 길이 ≤ 100
  • 3 ≤ board의 원소의 길이 ≤ 100
  • board의 원소의 길이는 모두 동일합니다.
  • 문자열은 ".", "D", "R", "G"로만 구성되어 있으며 각각 빈 공간, 장애물, 로봇의 처음 위치, 목표 지점을 나타냅니다.
  • "R"과 "G"는 한 번씩 등장합니다.

입출력 예

board                                                  	result
["...D..R", ".D.G...", "....D.D", "D....D.", "..D...."]	   7
[".D.R", "....", ".G..", "...D"]                          -1

나의 코드

function solution(board) {
   // 각 행의 원소를 변경할 일이 있을지도 몰라서 split했는데 그럴 일 없어서 불필요해진 코드
    board = board.map(r => r.split(''));
   // R과 G의 인덱스를 각각 targetIndex라는 함수로 반환 받아옴.
    const [xR, yR] = targetIndex(board, "R");
    const [xG, yG] = targetIndex(board, "G");
   // board와 크기가 같고 0으로 채워져있는 marking이라는 2차원 배열 선언
    let marking = [...Array(board.length)].map(_ => [...Array(board[0].length)].map(v => v = 0));

 // 4개 방향 dir 배열에 선언
    const dir = [[-1,0], [0,1], [1,0], [0,-1]];
  // 반복문을 돌리기 전에 queue에 R의 인덱스를 저장하고 시작
    let queue = [[xR, yR]];
    // while문 밖에 x,y를 미리 선언
    let [x,y] = [xR, yR];
    
      while(true){
      // 더 이상 방문할 곳이 없다면 ? queue의 길이가 0이 되고 G에 도달할 수 없음을 의미하므로 -1 반환
         if(queue.length === 0) return -1;
     // 반복문 매 회차 때마다 x, y는 queue에서 선입선출로 shift 해옴
         [x,y] = queue.shift();
     // marking에서 G의 인덱스에 해당하는 곳에 0이 아닌 숫자가 채워지면 해당 숫자 반환(시작점부터 몇 번만에 왔는지에 해당하는 숫자임)
         if(marking[xG][yG]) return marking[xG][yG];
     // for문으로 dir 배열 순회
         for(let i = 0;i < 4;i++){
            const [dx, dy] = dir[i];
            let [nx, ny] = [x, y];
   // 한 칸이라도 움직이려면 다음 스텝이 ".", "R", "G" 중 하나여야 함
          while(board[nx + dx]?.[ny + dy] === "." 
           || board[nx + dx]?.[ny + dy] === "R" 
           || board[nx + dx]?.[ny + dy] === "G"){
                   nx += dx;
                   ny += dy;
                }
           // 한 칸도 못움직인 경우 D이거나 out of range이므로 재빨리 다음 방향을 내놓으렴
            if(nx === x && ny === y) continue;
           // 이미 가본 적 있으면 재빨리 다음 방향을 내놓으렴
            if(marking[nx][ny] > 0) continue;
            
      // 지옥의 if문 통과 시 marking에 기존보다 1만큼 높은 숫자 표시
                 marking[nx][ny] = marking[x][y] + 1; 
     // 지옥의 if문을 통과한 당신이야 말로 queue로 push할 자격이 있어
                queue.push([nx, ny]);
            }
          }
}

// R과 G의 인덱스를 찾아줄 함수를 별도로 선언 (가독성 고려)
function targetIndex(board, target){
    const x = board.findIndex(row => row.includes(target));
    const y = board[x].indexOf(target);
    return [x, y];
}

이 문제를 처음에 어려워했던 이유는 while문 내부에서 한 걸음씩 갈 생각을 못하고 매 회차마다 한번에 D 바로 옆까지 가려고 했기 때문이었다. 미리 dir 배열을 정의해 놓고, 다음 스텝이 방문 가능한 스텝인 경우에 한해 계속 한 발자국 씩 움직인다고 생각하면 쉽(..지는 않)게 코드를 생각해낼 수 있었다.

그리고 또 하나 시간을 많이 잡아먹었던 점!
중간중간 자꾸 TypeError가 나는데 한참 뒤에야 원인을 알았다.

 [x,y] = queue.shift();
               ^

TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))

나는 코드를 맞게 쓰고 있는 것 같은데 외떼문에 TypeError가 났는가???

2번째 테스트 케이스가 아웃오브 안중이었기 때문이었다.
2번째 테케에서 더 이상 방문할 수 있는 곳이 없으면 queue의 길이가 0이 되어버리기 때문이다. 나는 첫번째 테케만 신경 쓰면서 풀고 있었기 때문에 에러의 원인도 첫번째 문제일 것이라고만 생각했다. 그러다가 문득 2번째 테케가 떠올라서 발견함 ㅠㅠ
이런 실수를 기록해놓고 인지하지 못하면 실전 코테에서도 시간을 많이 잡아 먹을 것 같다. 오류가 날 때 어떤 테스트케이스 때문에 나는 건지를 잘 파악할 것!
특히 저 undefined 이터러블하지 않다는 오류는 이제 위와 같은 원인이라면 헤매면서 시간 잡아먹지 말도록 하자.

오늘 프로그래머스 1레벨 공원 산책 문제를 풀어봤는데, 그 문제를 풀어봤던게 확실히 도움이 되었던 것 같다!
프로그래머스 1레벨 문제를 체육복을 마지막으로 다 풀었다고 생각했는데.. 계속 나도 모르는 사이에 추가가 되는 건지 몰랐다 ㅋㅋㅋㅋㅋ 여튼 도움이 되었던 문제였다!! 공원 산책!!

화이팅 쟤스민 ٩( ᐛ )و

profile
기록에 진심인 개발자 🌿

0개의 댓글