본문 바로가기

React

틱택톡 게임 구현하기

틱택토(tic-tac-toe)

 

틱택토 게임은 두 명이 번갈아가며 O와 X를 3x3 판에 써서 같은 글자를 가로, 세로, 혹은 대각선 상에 놓이도록 하는 놀이이다. 이번 글에서는 틱택토 게임을 진행할 수 있게 구현하고, 게임이 끝난 시점에서 승자를 결정하는 것과 다음 플레이어가 누구인지에 대해서도 나타내고자 한다.

 

 

 

자 이제 리액트로 코드를 구현해보도록 하자. 우리는 3개의 컴포넌트를 사용할 것이다 .

 

1.Game 컴포넌트

2. Board 컴포넌트

3. Square 컴포넌트

class Square extends React.Component {
            render(){
                return(
                    
                )
            }
        }

class Board extends React.Component {
    render(){
        return(

        )
    }
}

class Game extends React.Component {
    render(){
        return(

        )
    }
}

ReactDOM.render(
    <Game />,
    document.querySelector('#root')
)

 

 

 

여기서 이제 3개의 컴포넌트를 어떤 구성으로 짤지 그림으로 간략하게 보여주도록 하겠다.

 

 

Game 컴포넌트를 큰 틀로 두고 그 안에 Board 컴포넌트를 두고 Board 컴포넌트 안에 Square 컴포넌트를 두는 구성을 둘 것이다.

 

첫 번째로 Game 컴포넌트 안에 Board 컴포넌트를 넣고 <div>로 감싸도록 하자. 

  class Game extends React.Component {
            render(){
                return(
                    <div className="game">
                        <div className="game-board">
                            <Board />
                        </div>
                    </div>
                  
                )
            }
        }

 

자 그러면 이제 Game 컴포넌트가 실행이 될 때 Board 컴포넌트가 실행이 되는 것을 알 수 있다. 이제 Board 컴포넌트로 가보도록 하자.

 

 

 

Board 컴포넌트의 구성은 보도록 하자

 

틱택토 게임을 보면 정사작형 모양에 3줄로 채워져있고 9칸으로 나눠져 있는 것을 알 수 있다. 그래서 우리는 <div>엘리먼트로 3줄을 만들고 그 안에 3칸을 채울 수 있도록 로직을 구성해야 한다.

 

그리고 우리는 총 9칸에서 하나를 선택했을 때 'O' , ' X ' 가 순서대로 나오게하면서 둘 중 하나가 한줄로 채워지면 승자가 정해지는 로직을 포함할 것이다. 

 

class Board extends React.Component {

            state = {
                squares:Array(9).fill(null),
                xIsNext:true

            }

            handleClick = (i) => {
                const squares = [...this.state.squares]
                if(squares[i] || calcuateWinner(squares)){
                    return
                }

                const xIsNext = this.state.xIsNext

                squares[i] = xIsNext ? 'X' : 'O'
                this.setState({
                    ...this.state,
                    squares,
                    xIsNext:!xIsNext
                })
            }


            renderSquare = (i) => {
                return(
                    <Square 
                        value={this.state.squares[i]}
                        onClick={()=>{this.handleClick(i)}}
                    />     
                )
            }

            render(){
                const {renderSquare,state:{squares}} = this
                return(
                    <div>
                        <div className="status">{status}</div>
                        <div className="board-row">
                            {renderSquare(0)}
                            {renderSquare(1)}
                            {renderSquare(2)}
                        </div>
                        <div className="board-row">
                            {renderSquare(3)}
                            {renderSquare(4)}
                            {renderSquare(5)}
                        </div>
                        <div className="board-row">
                            {renderSquare(6)}
                            {renderSquare(7)}
                            {renderSquare(8)}
                        </div>
                    </div>
                )
            }
        }

 

 

Board Component 상태값으로 null값으로 채워진 배열을 9개 생성했다. 그리고 xIsNext 는 true 값을 기본값으로 두었다.

 

state = {
                squares:Array(9).fill(null),
                xIsNext:true

            }

 

 

 

3줄과 9개의 칸을 구성하고 이벤트 함수를 넣기 위해 renderSquare( ) 함수를 따로 만들어서 사용하도록 했다. renderSquare 함수에는 상태값에서 { this.state.squares[i] }로 배열을 가져와서 value라는 변수명으로 Square 컴포넌트에 props로 보내줬다. 추가로 handleClick이라는 함수또한 onClick이라는 변수명으로 Square 컴포넌트에 보내줬다.

 

renderSquare = (i) => {
                return(
                    <Square 
                        value={this.state.squares[i]}
                        onClick={()=>{this.handleClick(i)}}
                    />     
                )
            }

 

 

 

Square 컴포넌트에 보낸 handleClick 함수에 대해서도 알아보도록 하자 . handleClick 함수의 매개변수를 i 로 두고 i를 통해서 9개의 배열의 인덱스 값을 나타내도록 했다. 

 

그 다음 깊은복사 스프레드 연산자를 통해서 상태값에 있는 squares 값을 새로운 변수명인 squares에 넣었다. 

그 다음 xIsNext도 상태값에서 가져오도록 하자 => this.state.xIsNext

 

이 이벤트 함수를 넣은 이유가 플레이어가 9칸 중 하나를 눌렀을 때 ' X '가 뜨게 하고 다음 순서로 다른 플레이어가 다른 칸을 클릭했을 때 ' O ' 가 뜨게 하고, 이미 값이 나온 칸에는 더이상 선택이 되지 않도록 기능을 넣을려고 한다.

 

그걸 위한 코드가 아래와 같다.

handleClick = (i) => {
                const squares = [...this.state.squares]
                if(squares[i] || calcuateWinner(squares)){
                    return
                }

                const xIsNext = this.state.xIsNext

                squares[i] = xIsNext ? 'X' : 'O'
                this.setState({
                    ...this.state,
                    squares,
                    xIsNext:!xIsNext
                })
            }

상태값을 가져왔을 때 이 값을 변경하기 위해서는 setState() 문법을 사용해야 한다. 그 안에 깊은 복사로 state 값을 가져오고 sqaures를 가져왔으며

 

xIsNext : !xIsNext로 true와 false를 계속 반복하며 왔다갔다하게 설정해놨다. => 'X' 와 'O'을 반복적으로 실행하기 위해

 

 

 

 

 

 

자 이제 renderSquare 함수를 통해서 <Square /> 컴포넌트에 props로 던져주고 받는 작업을 해보자

 class Square extends React.Component {
            render(){
                return(
                    <button className="square"
                            onClick={this.props.onClick}
                    >
                            {this.props.value}
                    </button>
                )
            }
        }

 

 

 

 

자 이제 승자를 정하는 함수를 만들어 보자. 

 

calcuateWinner 함수를 만들어서 승자 공식을 만들어보자. 

const calcuateWinner = (squares) => {
            const line = [
                [0,1,2],
                [3,4,5],
                [6,7,8],
                [0,4,8],
                [2,4,6],
                [0,3,6],
                [1,4,7],
                [2,5,8]
            ]
            for(let i=0; i<line.length; i++){
                const [a,b,c] = line[i]
                if(squares[a] != null && squares[a] == squares[b] && squares[a] == squares[c]){
                    return squares[a]
                }
            }
            return null
        }

line 배열을 만들어서 승자가 정해지는 경우의 수를 배열로 만들었다. 

 

그리고 for문을 돌려서 0 부터 8까지 경우의 수를 돌려서 하나의 경우가 존재할 경우 승자를 정하는 공식이다.

 

 

 

 

 

이 함수를 Board 컴포넌트에 있는 handleClick 함수안에다가 넣어서 if문으로 calcuateWinner가 null 값이 아닐 경우 바로 코드가 종료됨으로써 승자가 결정이 될 수 있게 설정을 해줬다. 

handleClick = (i) => {
                const squares = [...this.state.squares]
                if(squares[i] || calcuateWinner(squares)){
                    return
                }

 

 

 

 

마지막으로 승자가 결정이 날 경우 그 결과화면을 브라우저에 렌더링하는 작업을 해보자.

Board 컴포넌트에 render안에다가 삼항연산자로 설정한 값들을 넣은 코드이다

render(){
                const {renderSquare,state:{squares}} = this
                const winner = calcuateWinner(squares)
                const status = winner != null ? `Winner : ${winner}` : `Next Player : ${this.state.xIsNext ? 'X' : 'O'}`
                return(
                    <div>
                        <div className="status">{status}</div>

 

 

 

 

아래의 사진이 결과로 나온 화면의 렌더링된 모습이다.

 

 

'React' 카테고리의 다른 글

React의 함수형 Component ( feat. Hooks )  (0) 2022.04.25
React 댓글 구현하기  (0) 2022.04.24
Class와 기본 문법  (0) 2022.04.22
Webpack 소개, 설치법  (0) 2022.04.21
리스트와 key  (0) 2022.04.21