본문 바로가기
Front End/React

[React] Lights Out : 셀의 불을 모두 끄자

by 옐 FE 2021. 8. 30.

 

노란색으로 된 셀이 불이 켜진거라는 가정하에 노란색 셀을 다 진한 회색으로 만들면 이기는 게임 그리고 완성된 결과물(이 게임에 자신이 없어서 끝에 YOU WON이 나오도록 조작함...). 사실 초반에는 설명만으로도 감을 잡을 수 없어서 강사분이 어떻게 짜는 지 보고 코드를 나름대로 짜봤다. 설명을 보기 전에 이런 결과물만 보고 어떤 식으로 component를 나눌지, defaultProps와 state는 어떤 것으로 정할 지 생각해보라고 했는데 이 때부터 감이 잡히지 않았다. 난 셀에다가 켜지고 꺼지는 state를 반영해야 하나 싶었는데 풀이방법은 여러가지가 있겠지만 셀은 담고 있는 보드를 만들 때부터 각각의 셀에 랜덤으로 true와 false의 값을 부여한다. 그리고 이 값으로 게임 시작 때부터 색상을 준다.

// index.js > App.js > Board.js > Cell.js로 Component를 구성

class Board extends Component {
  static defaultProps = {
    rows: 5,
    columns: 5,
    lightsOutRandomly: 0.25
  };

  constructor(props) {
    super(props);
    this.state = {
      board: this.setBoard(),
      hasWon: false
    };
  }

  setBoard() {
    const rows = Array.from({ length: this.props.rows });
    const columns = Array.from({ length: this.props.columns });
    const board = rows.map(() =>
      columns.map(() => Math.random() < this.props.lightsOutRandomly)
    );

    return board;
  }

 

 

 

그리고 의문이었던 것은 내가 누른 셀 주변의 셀들은 어떻게 동작하게끔 하는 가 싶었는데, 셀 자체 좌표를 부여해서 위치 파악을 할 수 있게 한다.  강사분은 for loop를 써서 row로 잡은 것 안에서 column을 loop하는 걸로 각각의 셀의 좌표를 주었는데, 난 map 함수를 사용하고 여기서 얻을 수 있는 index를 각각의 셀에 적용했다. 

createBoard() {
    const rows = Array.from({ length: this.props.rows });
    const columns = Array.from({ length: this.props.columns });
    const lightsBoard = rows.map((_, y) => (
      <tr key={y} className="Board-rows">
        {columns.map((_, x) => (
          <Cell
            flipCells={() => this.flipCellsAround(y, x)}
            key={`${y}-${x}`}
            lightsOut={this.state.board[y][x]}
          />
        ))}
      </tr>
    ));

    return lightsBoard;
  }

 

 

이렇게 각 셀마다 좌표를 가지고 있으니까 무엇이 바뀌어야 하는지 설정할 수 있다. 이를 바탕으로 가운데 셀을 클릭하면 클릭한 셀 포함, 이 셀의 주변에 있는(위, 아래, 양 옆) 셀들을 계산해서 바뀌는 식으로 동작. 

  flipCellsAround(y, x) {
    const { columns, rows } = this.props;
    const board = this.state.board;
    const flipCell = function(y, x) {
      if (x >= 0 && x < columns && y >= 0 && y < rows) {
        board[y][x] = !board[y][x];
      }
    };

    flipCell(y, x);
    flipCell(y - 1, x);
    flipCell(y, x + 1);
    flipCell(y + 1, x);
    flipCell(y, x - 1);

    const hasWon = board.every(row => row.every(column => !column));
    this.setState({ board, hasWon });
  }

 

 

 

Lights Out과 YOU WON의 스타일링은 codepen에서 'neon'으로 검색했을 때 나오는 코드를 복사해서 클래스 이름만 바꿔서 사용했다.

 

 


 

 

쓰고 나니 엄청 복잡한 코드는 아닌데 비슷한 걸 접한 적이 없으면 처음에 어떻게 구현할 지 조금 막막할 수도 있겠단 생각. (제가 그러했습니다...) 그리고 변수의 이름을 쓸 때 딱 맞는 이름을 간단하게 쓰고 싶은데 이름을 어떻게 적어야 할 까 고민하기도 했다. createBoard를 하면서 동시에 setBoard를 할 수 없을까 궁리했는데 결론은 각각 하는 게 낫지 않을까 싶다. 하나의 함수, 하나의 method에 너무 많은 기능을 담으려고 하는 건 좋지 않다고 했으니...

 

 


 

 

참고 : The Modern React Bootcamp / Udemy

Neon Flux / codepen

댓글