React

리액트로 사용자 vs 컴퓨터 Tic Tac Toe 틱택토 게임 구현하기 1편

히새 2024. 5. 7. 16:24

안녕하세요 희새입니다!

일이 있어서 (?) 리액트로 틱택토 게임을 구현해야 하는데, 헷갈려서 정리하고자 + 지피티에게 물어본 것들을 더 잘 이해하기 위해서 글을 씁니다 :)

 

제가 구현하고자 하는 틱택토 게임은 기존 리액트 공식문서 틱택토 게임에서

 

1. 컴퓨터와 대결

2. 승, 패에 따른 점수 기록

3. 게임 리셋

 

기능이 추가 된 틱택토를 구현하려고 한다.

 


이 기능을 위해 내가 해야하는 것을 정리해보자면

 

(한 칸을 square 라고 했을 때) square 클릭함수,

컴퓨터가 수 놓는 함수,

승패 구별 함수,

게임 리셋 함수,

score 업데이트

 

이렇게 인 것 같다.

 

게임 보드판, 현재 차례, 점수를 상태로 관리해 줄 것이다.

현재 차례는 사용자가 O, 컴퓨터가 X 


square 클릭 함수인 handleClick 함수이다.

// 전체 코드

const handleClick = (i) => {
  if (squares[i] || referee(squares) || currentPlayer !== 'O') {
    return;
  }
  const nextSquares = squares.slice();
  nextSquares[i] = 'O';
  setSquares(nextSquares);
  setCurrentPlayer('X');
}

 

  const handleClick = (i) => {
    if (squares[i] || referee(squares) || currentPlayer !== 'O') {
      return;
    }
}

 

- squares[i] : 클릭한 square 의 인덱스 값
- referee(squares) : referee 는 심판이라는 뜻으로 승패결정 함수이다.

- currentPlayer !== 'O' : 현재 플레이어가 사용자 = O 가 아닐 때


즉, 1. 클릭한 square 의 인덱스 값이 null 이 아닐 경우,
2. 승패 결정 함수가 값을 반환하는 경우 (=게임이 끝났을 때)

3. 사용자 차례가 아닐 경우에는
return 을 반환하여 함수를 종료시켜준다.

 

<< 현 시점에서 referee 함수를 작성하지 않았으니 2는 빼야 에러없이 실행된다! >>

  const handleClick = (i) => {
    if (squares[i] || referee(squares)) {
      return;
    }

    const nextSquares = squares.slice();
    
  }


- slice() : 배열의 일부 혹은 전체를 복사하여 새 배열로 반환, 복사본을 수정하여 결과를 반영할 때 사용됨
리액트에서는 상태 직접 변경을 피하기 때문에 이러한 방법을 사용해준다.

  const handleClick = (i) => {

    if (squares[i] || referee(squares)) {
      return;
    }
    
    const nextSquares = squares.slice();

    nextSquares[i] = 'O';
    }
  }


O 를 할당

 

  const handleClick = (i) => {
  
    if (squares[i] || referee(squares)) {
      return;
    }

    const nextSquares = squares.slice();

    nextSquares[i] = 'O';
    
    setSquares(nextSquares);
    
    setCurrentPlayer('X');
  }

SetSquares(nextSquares)

- 업데이트 된 squares 배열을 상태로 설정

 

SetCurrentPlayer(true);
- 차례 변경
지금의 경우 player 다음은 항상 computer 이기 때문에 항상 true = X 가 다음 플레이어로 할당되게 해줌
만약 사용자가 두 플레이어를 다 하고 싶다면 !currentPlayer 로 설정해주면 됨


Computer 가 패 놓기 함수인 computerMove 함수이다.

// 컴퓨터 차례
const computerMove = () => {
  // 빈 칸 찾기
  const emptySquare = squares.map((square,i) => square === null ? i : null).filter(i => i !== null);
  // 무작위로 한 칸 선택
  const randomSquare = emptySquare[Math.floor(Math.random() * emptySquare.length)];
  // 선택된 위치에 말 놓기 (X)
  const newSquares = squares.slice();
  newSquares[randomSquare] = 'X';
  // 보드 상태 업데이트
  setSquares(newSquares);
  // 차례 변경
  setCurrentPlayer('O');
}

 

알고리즘을 사용하지 않고 그냥 무작위 빈칸에 패를 놓게 한다.

빈 칸 찾기 -> 무작위로 한 칸 선택 -> 선택한 위치에 X 말 놓기 -> 보드 상태 업데이트 -> 차례 변경 순으로 작동한다.

 

useEffect(() => {
  if (currentPlayer === 'X') {
    const timer = setTimeout(computerMove, 1000);
    return () => clearTimeout(timer);
  }
}, [currentPlayer, squares]);

 

 

useeffect 를 사용하여 currentPlayer 가 X = 컴퓨터 면 1초 뒤에 computerMove 함수를 실행하게 해주었다.

 


return 문 작성

return(
  <>
    <div className='text-center'>
      <h3>Score : {score}</h3>
      <h2>current player : {currentPlayer}</h2>
    </div>
    <section>
      <div className='board-row'> {[0,1,2].map( i => <Square key={i} value={squares[i]} onClick={() => handleClick(i)} />)} </div>
      <div className='board-row'> {[3,4,5].map( i => <Square key={i} value={squares[i]} onClick={() => handleClick(i)} />)} </div>
      <div className='board-row'> {[6,7,8].map( i => <Square key={i} value={squares[i]} onClick={() => handleClick(i)} />)} </div>
    </section>
  </>
)

 

score 는 나중에 점수를 보여주기 위해서 작성해두었다.

div 세 개로 칸을 나눈 이유는 그냥 3 * 3 편하게 하기 위해서라고 한다. ( 지피티가 말해줌 )

 

 

여기까지 하면이렇게..! 나랑 컴퓨터랑 번갈아가면서 패를 놓는 틱택토 게임이 되었다.

승/패 구별 함수가 없어서 컴퓨터가 한 줄을 완성시켰는데도 계속 게임이 진행된다.

2편에서는 승 / 패를 구별하고 그에 따라 score 값을 내리거나 올리고, 게임이 종료된 후 리셋까지 완성시켜보도록 하겠습니당

 

글 읽어주셔서 감사합니다 (--) (__)

 


 

만드는 과정 중에 생긴.. 컴퓨터만 패를 쌈@뽕하게 놓는 그런.. 코딩 :) 폭주기관차 컴퓨터 푸하하