본문 바로가기
💻CODING/react. vue

[react] react로 가위바위보 게임 만들기

by 코딩하는 갓디노 2021. 4. 1.

가위 바위 보

 

리액트 class/hooks로 구현한
가위바위보 게임 입니다.

 

예제는 인프런의 제로초, "조현영"님의 강의를 들으면서 공부한 내용입니다.


client.jsx

import React from 'react';
import ReactDom from 'react-dom';
import RSP from './RSP';

ReactDom.render(<RSP />, document.querySelector('#root'));

 

index.html

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>가위 바위 보</title>
    <style>
        #computer { height: 250px; width: 140px; }
    </style>
</head>

<body>
    <div id="root"></div>
    <script src="./dist/app.js"></script>
</body>

</html>

 

RSP.jsx - class 방식(before 리팩토링)

import React, { Component } from 'react';

//클래스 경우 -> constructor -> render -> ref -> componentDidMount
// -> (setState/props 바뀔때) -> showComponentUpdate(return true 일 경우) -> render -> componentDidUpdate
// 부모가 나를 없앴을때 -> componentWillUnmount -> 소멸

const rspCoords = {
    바위: 0,
    가위: -100,
    보: -270,
};

const scores =  {
    가위: 1,
    바위: 0,
    보: -1,
};

const computerChoice = (imgCoord) => {
    return Object.entries(rspCoords).find(function(v) {
        return v[1] === imgCoord;
    });
};

class RSP extends Component {
    state = {
        result: '',
        score: 0,
        imgCoord: 0, //이미지 좌표
    }

    componentDidMount() { //처음 렌더링 된 직 후, 비동기 요청
        this.interval = setInterval( () => {
        //위에가 {} 안에 있어야 함(setInterval가 비동기이므로 클로저 문제)
            const { imgCoord } = this.state; 
            if(imgCoord === rspCoords.바위) {
                this.setState({
                    imgCoord: rspCoords.가위,
                });
            }else if(imgCoord === rspCoords.가위) {
                this.setState({
                    imgCoord: rspCoords.보,
                });
            }else if(imgCoord === rspCoords.보) {
                this.setState({
                    imgCoord: rspCoords.바위,
                }); 
            }
        }, 200);
    }
 	//컴포넌트가 제거되기 직전, 비동기 요청 수동으로 clear 처리 해야 함
    componentWillUnmount() { 
        clearInterval(this.interval);
    } 

    onClickBtn = (choice) => { //함수, choice = 바위가위보
        clearInterval(this.interval);
        const myScore = scores[choice];
        const computerScore = scores[computerChoice(imgCoord)];
        const diff = myScore - computerScore;
        if(diff === 0) {
            this.setState({
                result: '비겼습니다.' 
            })
        }else if ([-1, 2].includes(diff)) {
            this.setState((prev) => {
                return {
                    result: '이겼습니다.',
                    score: prev.score + 1,
                }
            });
        }else{
            this.setState((prev) => {
                return {
                    result: '졌습니다',
                    score: prev.score + 1,
                }
            })
        }
    };

    render() {
        const { result, score, imgCoord } = this.state;
        return (
            <div>
                <div id="computer"
                    style={{
                        background: `url(https://blog.kakaocdn.net/dn/dEfPIp/btqRxZvIwfQ/
                        ZxcvaXl4f4TG4S08igTeak/img.jpg) 
                        ${imgCoord}px 0`
                    }}></div>
                <div>
                    <button id="rock" className="btn" onClick={() => onClickBtn('바위')}>바위</button>
                    <button id="scissor" className="btn" onClick={() => onClickBtn('가위')}>가위</button>
                    <button id="paper" className="btn" onClick={() => onClickBtn('보')}>보</button>
                </div>
                <div>{result}</div>
                <div>현재 점수: {score}점</div>
            </div>
        );
    }
}

export default RSP;

 

RSP.jsx - class 방식(after 리팩토링)

import React, { Component } from 'react';

const rspCoords = {
    바위: 0,
    가위: -100,
    보: -270,
};

const scores =  {
    가위: 1,
    바위: 0,
    보: -1,
};

const computerChoice = (imgCoord) => {
    return Object.entries(rspCoords).find(function(v) {
        return v[1] === imgCoord;
    })[0];
};

class RSP extends Component {
    state = {
        result: '',
        score: 0,
        imgCoord: 0, //이미지 좌표
    }

    componentDidMount() { //처음 렌더링 된 직 후, 비동기 요청
        this.interval = setInterval( this.timeCtr, 200);
    }

    timeCtr = () => {
        const { imgCoord } = this.state; 
        if(imgCoord === rspCoords.바위) {
            this.setState({
                imgCoord: rspCoords.가위,
            });
        }else if(imgCoord === rspCoords.가위) {
            this.setState({
                imgCoord: rspCoords.보,
            });
        }else if(imgCoord === rspCoords.보) {
            this.setState({
                imgCoord: rspCoords.바위,
            }); 
        }
    }
 
    componentWillUnmount() { 
        clearInterval(this.interval);
    } 

    onClickBtn = (choice) => () => { //리팩토링 함수, choice = 바위가위보
        const {imgCoord} = this.state; //구조분해
        clearInterval(this.interval);
        const myScore = scores[choice];
        const computerScore = scores[computerChoice(imgCoord)];
        const diff = myScore - computerScore;
        if(diff === 0) {
            this.setState({
                result: '비겼습니다.'
            })
        }else if ([-1, 2].includes(diff)) {
            this.setState((prev) => {
                return {
                    result: '이겼습니다.',
                    score: prev.score + 1,
                }
            });
        }else{
            this.setState((prev) => {
                return {
                    result: '졌습니다',
                    score: prev.score - 1,
                }
            })
        }
        setTimeout(() => {this.interval = setInterval(this.timeCtr, 200)},1000);
        
    };

    render() {
        const { result, score, imgCoord } = this.state;
        return (
            <div>
                <div id="computer"
                    style={{
                        background: `url(https://blog.kakaocdn.net/dn/dEfPIp/btqRxZvIwfQ
                        /ZxcvaXl4f4TG4S08igTeak/img.jpg) ${imgCoord}px 0`
                    }}></div>
                <div>
                    {/* onClick 메서드 안에 함수를 호출하는 부분 리팩토링 () => 57번으로 자르고 복사 */}
                    <button id="rock" className="btn" onClick={this.onClickBtn('바위')}>바위</button>
                    <button id="scissor" className="btn" onClick={this.onClickBtn('가위')}>가위</button>
                    <button id="paper" className="btn" onClick={this.onClickBtn('보')}>보</button>
                </div>
                <div>{result}</div>
                <div>현재 점수: {score}점</div>
            </div>
        );
    }
}

export default RSP;

 

RSP.jsx -hooks 방식

import React, { useState, useRef, useEffect } from 'react';

const rspCoords = {
    바위: 0,
    가위: -100,
    보: -270,
};

const scores =  {
    가위: 1,
    바위: 0,
    보: -1,
};

const computerChoice = (imgCoord) => {
    return Object.entries(rspCoords).find(function(v) {
        return v[1] === imgCoord;
    })[0];
};

const RSP = () => {
    const [result, setResult] = useState('');
    const [score, setScore] = useState(0);
    const [imgCoord, setImgCoord] = useState(rspCoords.바위); //rspCoords.바위 = 0;
    const interval = useRef();

    useEffect( () => { //componetDidMount, componentDidUpdate 역할(1대1 대응은 아님)
        interval.current = setInterval(timeCtr, 200);
        return () => { //componentWillUnmount 역할
            clearInterval(interval.current);
        }         
    }, [imgCoord]); //[] 값 = 바뀌는 state, 클로저 문제를 해결

    const timeCtr = () => {
        if(imgCoord === rspCoords.바위) {
            setImgCoord(rspCoords.가위);
        }else if(imgCoord === rspCoords.가위) {
            setImgCoord(rspCoords.보);
        }else if(imgCoord === rspCoords.보) {
            setImgCoord(rspCoords.바위);
        }
    };

    const onClickBtn = (choice) => () => {
        clearInterval(interval.current);
        const myScore = scores[choice];
        const computerScore = scores[computerChoice(imgCoord)];
        const diff = myScore - computerScore;
        if(diff === 0) {
            setResult('비겼습니다.');
        }else if ([-1, 2].includes(diff)) {
            setResult('이겼습니다.');
            setScore((prevScore) => prevScore + 1);
        }else{
            setResult('졌습니다.');
            setScore((prevScore) => prevScore -1);
        }
        setTimeout(() => {interval.current = setInterval(timeCtr, 200)},1000);
    }

    return (
        <div>
            <div id="computer"
                style={{
                    background: `url(https://blog.kakaocdn.net/dn/dEfPIp/btqRxZvIwfQ
                    /ZxcvaXl4f4TG4S08igTeak/img.jpg) ${imgCoord}px 0`
                }}></div>
            <div>
                {/* onClick 메서드 안에 함수를 호출하는 부분 리팩토링 () => 57번으로 자르고 복사 */}
                <button id="rock" className="btn" onClick={onClickBtn('바위')}>바위</button>
                <button id="scissor" className="btn" onClick={onClickBtn('가위')}>가위</button>
                <button id="paper" className="btn" onClick={onClickBtn('보')}>보</button>
            </div>
            <div>{result}</div>
            <div>현재 점수: {score}점</div>
        </div>
    );
}

export default RSP;
반응형

댓글