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