슬라이딩 퍼즐 게임 만들기 #3/4 (타일 섞기)

천둥상어

·

2024. 1. 5. 23:51

반응형

슬라이딩_퍼즐_만들기_썸네일

 

셔플 조건

타일의 움직임을 구현했으니

이제 섞기(Shuffle)을 구현할 차례이다.

 

언뜻 생각하면 타일의 순서를

아무렇게나 섞으면 될것 같지만

그렇게 하면 안 된다.

 

슬라이딩 퍼즐은 다음과 같은 조건이 있다.

출처는 나무위키다.

 

NxN의 형태로 배치하였고, 

'자신보다 큰 수가 자신의 앞에 있는 개수'의 합계가 I이며,

빈 칸에 해당하는 X가 있을 떄 다음과 같다.

  • N이 홀수, I가 짝수이면 풀 수 있는 배치.
  • N이 짝수,  X가 맨 아래 짝수 칸, I가 홀수이면 풀 수 있는 배치.
  • N이 짝수, X가 맨 아래 홀수 칸, I가 짝수이면 풀 수 있는 배치.
  • 그 외에는 전부 불가능.

 

정말로 불가능한지 테스트 해보자.

아래와 같이 코드를 수정하여

타일 순서를 의도적으로 바꿔 진행해 본다.

 

testArr 배열을 하나 선언해서 타일 순서를 넣어주고

setText 메서드 호출 시 순서대로 넣어주는 코드이다.

  // 초기화 함수
  init() {
    this.tileArr = [];
    let posX = 0; //타일 x값
    let posY = 0; //타일 y값
    
    // 추가
    const testArr = [0,1,2,3,4,5,6,7,8,9,10,14,12,13,11,15]

    for (let i = 0; i < this.TOTAL_CELL; i++) {
      this["tile_" + i] = new Tile();
      const tile = this["tile_" + i];
      tile.answerIndex = i;
      tile.currentIndex = i;
      //tile.setText(String(i));
      tile.setText(String(testArr[i]));
      // 아래 생략
    }

 

실행하면 위와 같이 배치가 된다.

이 상태에서 풀어보려고 하면

절대 퍼즐을 풀 수가 없다.

즉 아무렇게나 배치하면 안 된다.

 

셔플 방법

알고리즘을 잘 짜서

로직을 구현하면 좋겠지만...

 

안타깝게도 나의 수학 능력은

그리 출중하지 못하다.

 

세련된 방법은 아니지만

비어있는 타일을 기준으로

해당 타일이 이동 가능한 위치 중 하나를

랜덤으로 가져와서 계속 이동시킬 것이다.

 

setShuffle() 함수 선언 및 이동 가능 여부 판단

일단 setShuffle() 메서드를 하나 새로 선언했다.

코드 검증을 위해서 for문은 1회만 실행한다.

 

비어 있는 타일 (15번)을 중심으로 상, 하, 좌, 우

이동이 가능한지 판단한다.

 

로직은 이전에 구현했던 moveTile()과 유사하다.

왼쪽 이동 가능 판단할 때 -1 보다 커야 한다는 조건이 있는데,

같은 줄 판단 시 음수도 0이 나오기 때문에 추가한 조건이다.

  setShuffle()
  {
    for(let i = 0; i < 1; i++)
    {
      const emptyIndex = this.emptyIndex;
      const line = Math.trunc(emptyIndex / this.row);
      const upIndex = emptyIndex - this.column;
      const downIndex = emptyIndex + this.column;
      const leftIndex = emptyIndex - 1;
      const rightIndex = emptyIndex + 1;

      if(downIndex < this.TOTAL_CELL )
      {
        console.log("아래로 이동 가능");
      } 

      if(upIndex > 0)
      {
        console.log("위로 이동 가능");
      }

      if(line === Math.trunc(rightIndex/ this.row))
      {
        console.log("오른쪽 이동 가능");
      }
    
      if(line === Math.trunc(leftIndex / this.row) && leftIndex > -1)
      {
        console.log("왼쪽 이동 가능");
      }
    }
  }

이제 init() 메서드 맨 아래에 setShuffle() 을 호출해 주자.

실행하면 위(11번)와 왼쪽(14)으로 이동 가능하다고 출력이 된다.

  // 초기화 함수
  init() {
    // 위 생략
    this.emptyIndex = this.TOTAL_CELL - 1;
    this["tile_" + this.emptyIndex].setVisible(false);

    this.puzzleContainer.x = 50;
    this.puzzleContainer.y = 50;

    this.setShuffle(); // 셔플 함수 호출
  }

 

이동 가능한 방향 중에서 랜덤으로 하나 고르기

위(11번)과 왼쪽(14) 번으로 이동이 가능하다면

둘 중 하나를 선택해서 이동하면 된다.

 

코드의 흐름은 다음과 같다.

  • for문이 실행될 때 이동 가능한 index를 담는
    배열 rndMove를 선언한다.
  • 방향별로 이동이 가능하다면 rndMove에 담아준다.
  • rndMove 배열 길이 범위 안에서 난수를 발생시킨다.
    발생된 난수는 변수 rnd에 넣어준다.
  • 해당 난수로 rndMove의 값을 가져온다.

최종적으로 변수 nextMoveIndex에

다음 이동할 index 값이 할당된다.

  setShuffle()
  {
    for(let i = 0; i < 1; i++)
    {
      const emptyIndex = this.emptyIndex;
      
      const rndMove = [];

      const line = Math.trunc(emptyIndex / this.row);
      const upIndex = emptyIndex - this.column;
      const downIndex = emptyIndex + this.column;
      const leftIndex = emptyIndex - 1;
      const rightIndex = emptyIndex + 1;

      if(downIndex < this.TOTAL_CELL )
      {
        console.log("아래로 이동 가능");
        rndMove.push(downIndex);
      } 

      if(upIndex > 0)
      {
        console.log("위로 이동 가능");
        rndMove.push(upIndex);
      }

      if(line === Math.trunc(rightIndex/ this.row))
      {
        console.log("오른쪽 이동 가능");
        rndMove.push(rightIndex);
      }
    
      if(line === Math.trunc(leftIndex / this.row) && leftIndex > -1)
      {
        console.log("왼쪽 이동 가능");
        rndMove.push(leftIndex);
      }
      
      const rnd = Math.floor(Math.random() * rndMove.length);
      const nextMoveIndex = rndMove[rnd];

      console.log(nextMoveIndex);
    }
  }

타일 이동 시키기

이제 nextMoveIndex를 가지고 실제로 타일을 움직이자.

방법은 간단하다.

이전에 만들었던 moveTIle 메서드를 호출하면 된다.

 

nextMoveIndex는 비어 있는 타일에 인접한 타일의 값이다.

결과적으론 해당 타일을 클릭하는 것과 동일하게 작동하는 셈이다.

  setShuffle()
  {
      // 위 생략
      console.log(nextMoveIndex);
      this.moveTile(nextMoveIndex, emptyIndex);
    }
  }

 

셔플 로직 개선

지금은 for문이 한 번만 돌기 때문에 한 번만 셔플이 일어나고 있다.

반복 조건을 2로 고치고 nextMoveIndex 값이 어떻게 출력 되는지 본다.

 

계속 새로고침으로 시도를 하다 보면 다음과 같은 경우가 발생한다.

셔플이 2번 발생했는데 타일이 하나도 섞이지 않았다.

로그를 보면 그 이유를 알 수가 있다.

위로 이동 후 다시 아래로 이동해서 원 위치가 된 것이다.

이전 위치는 이동 가능 범위에서 빼주자.

 

이전 값을 가지고 있는 prevIndex 변수를 하나 선언한다.

셔플이 일어나기 전이므로  의미 없는 -1을 넣어두자.

 

방향 조건문에서 prevIndex 값과 동일하지 않은 경우에만

rndMove 배열에 해당 인덱스 값을 넣어주도록 수정한다.

 

nextMoveIndex 값이 결정되고 나서

prevIndex에 emptyIndex 값을 대입하여 갱신한다.

이 rndMove에는 이동이 가능해도

이전 위치라면 포함되지 않는다.

  setShuffle()
  {
    let prevIndex = -1;

    for(let i = 0; i < 2; i++)
    {
      const emptyIndex = this.emptyIndex;
      
      const rndMove = [];

      const line = Math.trunc(emptyIndex / this.row);
      const upIndex = emptyIndex - this.column;
      const downIndex = emptyIndex + this.column;
      const leftIndex = emptyIndex - 1;
      const rightIndex = emptyIndex + 1;

      if(downIndex < this.TOTAL_CELL )
      {
        if(prevIndex != downIndex)
        {
          console.log("아래로 이동 가능");
          rndMove.push(downIndex);
        }
      } 

      if(upIndex > 0)
      {
        if(prevIndex != upIndex) 
        {
          console.log("위로 이동 가능");
          rndMove.push(upIndex);
        }
      }

      if(line === Math.trunc(rightIndex/ this.row))
      {
        if(prevIndex != rightIndex)
        {
          console.log("오른쪽 이동 가능");
          rndMove.push(rightIndex);
        }
      }
    
      if(line === Math.trunc(leftIndex / this.row) && leftIndex > -1)
      {
        
        if(prevIndex != leftIndex) 
        {
          console.log("왼쪽 이동 가능");
          rndMove.push(leftIndex);
        }
      }
      
      const rnd = Math.floor(Math.random() * rndMove.length);
      const nextMoveIndex = rndMove[rnd];

      prevIndex = emptyIndex;
      
      console.log(nextMoveIndex);
      this.moveTile(nextMoveIndex, emptyIndex);
    }
  }

 

로그에서도 11번 이동 후 아래 이동은

제외 되고 있음을 확인 할 수 있다.

 

셔플 수 조정

이제 셔플 회수를 늘려준다.

for문 반복수를 30정도 주면 적절하다.

 

이로써 셔플 로직을 구현했다.

세련된 방법은 아니지만

심플하면서 직관적인 로직이다.

 

다음 글에서는 정답을 체크하고 완성하자.

반응형