숫자 맞추기 게임 / 스마트 컨트렉트 배포

이재영·2023년 10월 6일
0

BlockChain

목록 보기
11/13

게임 룰

랜덤 숫자 범위 : 100 ~ 999
숫자를 입력 후, 게임시작을 누를 때마다 가격은 5이더
총 5번의 기회가 있고, 맞추면 총 상금을 가져가고, 틀리면 참가비 5이더가 총 상금에 쌓인다.


구현 코드

contracts > Baseball.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

contract Baseball{
    
    // 컨트렉트 배포자
    address private owner;

    // 게임의 횟수
    // ⭐constant 구문을 추가하면 상태를 변경하지 않을 상태 변수 , const 랑 같음.
    uint256 private constant GAME_COUNT = 5;

    // ticket 게임을 하고 싶으면 지불해야하는 이더
    uint256 private ticket = 5 ether ;

    // 정답의 값을 담아놓을 변수
    uint256 private random;
    
    // 게임의 진행도
    uint256 private progress;
    
    // 총 모여있는 상금
    uint256 private reward;

    // 게임의 현재 상태
    enum GameStatus {
        Playing,  // 0
        GameOver // 1
    }
    // 최초의 상태값은 0 - > 게임 플레이중
    GameStatus gameStatus;

    // 컨트렉트 생성자
    // ⭐ 컨트렉트가 배포되면 딱 한번만 실행되는데
    constructor() {
        // 최초에 딱한번 배포자가 상태변수에 담기고
        owner = msg.sender;

        // ⭐keccak256: 솔리디티에서 랜덤값을 뽑을 때 사용, 매개변수를 해시값으로 변경해준다. SHA-3
        // ⭐abi.encodePacked : 매개변수로 전달된 내용들을 가지고 바이트 배열로 만들어준다.
        // ⭐해시값을 uint256 형태로 변환
        random = uint256(keccak256(abi.encodePacked(
            block.timestamp, block.difficulty, block.coinbase ,block.number)
        ));

        // 100 ~ 999까지의 범위를 지정을 할거다.
        random = (random % 900) + 100;
    }

    // 유저의 값을 받아서 비교를 통해 값이 맞는지 게임의 정답을 맞췄는지 비교할 함수.
    function gameStart(uint256 _value) public payable {
        // ⭐require ()조건이 맞으면 통과(true면 아무일도 일어나지않고,계속 계약의 실행이 진행) , 아니면 오류 발생.
        require(progress < GAME_COUNT , "GameOver");
        require(msg.value == ticket, "ticket amount error (5 ether)");
        require((_value >= 100) && (_value < 1000),"_value error (99< _value < 1000)");
        // 게임 라운드 증가
        progress +=1;

      	// 입력 값과 random 값이 같으면 => 정답
        if (_value == random){
            // CA의 잔액이 보상 만큼 있는지 검사
            // ⭐address(this).balance 현재 스마트 계약의 이더 잔액
            require(reward <= address(this).balance);
            
            // ⭐payable을 사용하면 transfer(),send() 함수를 이용해 이더 전송가능.
            payable(msg.sender).transfer(address(this).balance);
            reward = 0;
            gameStatus = GameStatus.GameOver;
          
        } else{
            reward += msg.value;
        }
    }
    // 지금 현재 쌓인 보상을 보여줄 함수
    function getReward() public view returns (uint256){
        return reward;
    }
    
    // 게임이 얼마나 진행됬는지 보여줄 함수
    function getProgress() public view returns (uint256){
        return progress;
    }

    // 티켓의 금액을 보여줄 함수
    function getTicketPrice() public view returns (uint256){
        return ticket;
    }
  
    // 어드민 모드 : 정답을 확인하는 함수
    function getRandom() public view returns (uint256){
      // ⭐ require 문을 통과하지 못했을 경우에는, 두번째 인자의 오류를 반환한다.
        require(owner == msg.sender, "aaddmmiinn");
        return random;
    }
    
  	// 트랜잭션 생성자 주소를 확인하는 함수
    function getSender() public view returns (address){
        return msg.sender;
    }

    // 게임중인지 확인할 함수
    function getPlaying() public view returns (uint256){
    
        // 게임이 진행되고 있는 상수값이 0
        uint256 Playing =0;
      
      	// 게임이 끝나는 조건
        if((gameStatus != GameStatus.Playing) || (progress == GAME_COUNT)){
            Playing =1;
        }
        return Playing;
    }

    // 게임 재시작
    function gameRestart(uint256 _message) public {
      	// 게임이 종료 되어야지만 재시작 가능
        require(_message == 1 , "no endgame");
        if(_message == 1){
        // 재시작 시 진행도 초기화, 상태 게임중으로 변경, 랜덤 숫자 재설정.
        progress = 0;
        gameStatus = GameStatus.Playing;

        random = uint256(keccak256(abi.encodePacked(
            block.timestamp, block.difficulty, block.coinbase ,block.number)
        ));

        random = (random % 900) + 100;
    }
    }
}

src > App.js

import { useEffect, useState } from "react";
import useWeb3 from "./hooks/web3.hook";
import abi from "./abi/Baseball.json";

const App = ()=>{

    const {user,web3} = useWeb3();
    const [ticket, setTicket] = useState("0");

    // 우리가 입력해서 매개변수로 요청할 값, 우리가 정한 정답
    const [value , setValue] = useState("");
	
  	// 게임 보상
    const [reward, setReward] = useState("0");

    // 게임을 몇판이나 사람들이 진행했는지
    const [progress, setProgress] = useState("0");

    // 컨트렉트 배포자만 알 수 있는 답
    const [random, setRandom] = useState("000");

    // 게임이 진행중인지 여부
    const [message, setMessage] = useState("");

  	// ⭐ 스마트 계약 객체를 담을 변수
    const [baseballCountract, setBaseballCountract] = useState(null);

    const [sender ,setSender] = useState("");

    useEffect(()=>{
        if(web3 !==null){
            if(baseballCountract === null){
              // ⭐ new web3.eth.Contract() : 스마트 계약 객체를 생성하는 메서드
                const Baseball = new web3.eth.Contract(
                  // ⭐ 매개 변수는 abi , CA , options 이 순서대로 들어감.
                    abi, "0xC8D1381287B2e774a810B811E31eb2B6f04b0c23", {data : ""})
                setBaseballCountract(Baseball);
            }
        }
    },[web3]);

  //  ⭐ 객체를 생성하여 호출하여 사용하면 기존에 find 로 찾고, encodeFunctionCall로 인코딩하고,
  //      call로 스마트 컨트렉트를 호출하는 과정이 압축된다. ⭐
    const getTicket = async() => {
        if(baseballCountract === null) return;
        const result = web3.utils.toBigInt(await baseballCountract.methods.getTicketPrice().call()).toString(10);

        setTicket(await web3.utils.fromWei(result, "ether"));
    };

    const getReward = async() =>{
        if(baseballCountract === null) return;
        const result = web3.utils.toBigInt(await baseballCountract.methods.getReward().call()).toString(10);

        setReward(await web3.utils.fromWei(result, "ether"));
    }

    const getPlaying = async() =>{
        const playing = web3.utils.toBigInt(await baseballCountract.methods.getPlaying().call()).toString(10);
        setMessage(playing);
    }

    const getProgress = async () =>{
        const progress = web3.utils.toBigInt(await baseballCountract.methods.getProgress().call()).toString(10);
        setProgress(progress);
    };
    
    const getRandom = async () =>{
        try {
            const Random = web3.utils.toBigInt(await baseballCountract.methods.getRandom().call({
              //  ⭐ from 옵션을 넣지 않으면, msg.sender 에 트랜잭션 발생자의 주소가 담기지 않는다.
                from : user.account,
            })).toString(10);
            setRandom(Random);
            
        } catch (error) {
          // ⭐ require 문에서 반환된 오류는 error.data.message 에 포함되어 반환된다.
            console.log(error.data.message);
            if(error.data.message.includes("aaddmmiinn")){
                alert("어드민만 숫자를 볼 수 있음.")
            }
        }
    }

    const gameStart = async() => {
        if(value.length < 3){
            alert("숫자 3자리 입력해라");
            return;
        }
      //⭐ value 를 숫자로 변환 후 매개변수로 전달.
        await baseballCountract.methods.gameStart(Number(value)).send({
            from : user.account,
            //  ⭐ value : 스마트 계약에 이더를 송금할 때, 사용되는 속성
            value : web3.utils.toWei("5", "ether")
        })
        render();

    }
    const gameRestart = async() =>{
        try {
          // ⭐ send 함수만 있으면 가스비를 지불하기 떄문에 바로 error가 안나오고 
          // 메타마스크가 뜨기떄문에 call 함수로 먼저 판별.
            await baseballCountract.methods.gameRestart(Number(message)).call({
                from: user.account,
            });
            await baseballCountract.methods.gameRestart(Number(message)).send({
                from: user.account,
            });
            render();
        } catch (error) {
          
            if (error.data.message.includes("no endgame")) {
                alert("게임이 종료되어야 재시작 가능");
            }
        }
    }
    const getSender = async () =>{
        const Sender = await baseballCountract.methods.getSender().call({
            from : user.account,
        });
        setSender(Sender);
    }
    
    const render = async () =>{
        getTicket();
        getReward();
        getPlaying();
        getProgress();
    }

    useEffect(()=>{
        if(baseballCountract !=null){
            render();
        }
    },[baseballCountract])

    if(user.account === null) return " 지갑 연결 하세요. "
    return(
        <>  
            <div>{user.account}</div>
            <div>티겟 가격 : {ticket}</div>
            <div>현재 게임 진행도 : {progress} / 5</div>
            <div>총 상금 : {reward}</div>
            <div>진행상태 : {message == 0 ? "게임중 ": "게임종료"}</div>
            <div>sender : {sender}</div>

            <input onChange={(e)=>{setValue(e.target.value)}}></input>
            <div>정답 : {random} </div>
            <button onClick={gameStart}>게임시작</button>
            <button onClick={getRandom}>어드민</button>
            <button onClick={gameRestart}>게임 재시작</button>
            <button onClick={getSender}>sender</button>
        </>
    );

};

export default App;
profile
한걸음씩

0개의 댓글