1

I am trying to write a game on smart contract which is a game in which the player has to type in 5 guesses, and based on how close they were to the actual number, they would receive ether as prize.

GuessTheNumber:

// SPDX-License-Identifier: MIT
pragma solidity >= 0.7.0 < 0.9.0;

contract Oracle {

    address owner;
    uint public variableRand;

    constructor () {
        owner = msg.sender;
    }

    function setRandomVariable (uint _varRand) external {
        require (msg.sender == owner);
        variableRand = _varRand;
    }

}

contract Play {

    // Saves the value of what you have won in the session
    address owner;
    uint public currentPrize = 0;
    int[] public currentGuess;
    uint[] public randNum;

    event Log(uint gas);

    function showPoolBalance() public view returns(uint) {
        return address(this).balance;
    }

    function randMod() public view returns(uint) {
        // grab information from the blockchain randomly to generate random numbers - we need something dynamically changing
        // abi.encodePacked concatonates arguments nicely
        return uint(keccak256(abi.encodePacked(oracle.variableRand, block.timestamp, block.difficulty, msg.sender))) % 1000;
    }

    function inputGuess(int _guess) public {
        // Adds the guesses to the array
        currentGuess.push(_guess);
    }

    function clearGuesses() public {
        for (uint i = 0; i < currentGuess.length; i++) {
            //removes all of the guesses in the array
            currentGuess.pop();
        }
    }

    function abs(int x) private pure returns (int) {
        //returns the abolute value of the input
        return x >= 0 ? x : -x;
    }

    function game() private returns (int, uint) {

        for (uint i = 0; i <= 5; i++) {
            randNum[i] = randMod();
            int absv = abs(int(randNum[i]) - currentGuess[i]);
           if (absv < 6 && absv > 0) {
               currentPrize += 2;
           }
           else if (absv == 0) {
               currentPrize += 4;
           }
           else if (absv < 16 && absv > 5) {
               currentPrize += 1;
           }
           return (absv , randNum[i]);
        }

    }

    function play() public payable returns (int, uint) {

        require(msg.value == (10 ether), "You must pay with 10 ethers!");
        game();
        
    }

    function chargePool() public payable {
        require (msg.value >= 10 ether);
    }

    function withdrawPrize() public payable {

        payable(msg.sender).transfer(currentPrize);

        }

    fallback () external payable {
        emit Log(gasleft());
    }
    
    Oracle oracle;

    constructor (address oracleAddress) {
        oracle = Oracle(oracleAddress);
        owner = msg.sender;
    }

}

The Oracle contract deploys correctly. and the Play contract would deploy as well. The guessed numbers would be pushed in the array correctly but when I wanna call the play function with 10 ether the following error comes up :

[vm]from: 0x5B3...eddC4to: Play.play() 0xf8e...9fBe8value: 10000000000000000000 weidata: 0x93e...84cd9logs: 0hash: 0xfd3...dcad2 transact to Play.play errored: VM error: revert.

revert The transaction has been reverted to the initial state. Note: The called function should be payable if you send value and the value you send should be less than your current balance. Debug the transaction to get more information.

Any help would be appreciated.

1 Answer 1

0

I figured out what might be going wrong in your code.

It's the use of the randNum array. It's dynamic, so you can use .push() to add elements to it, but you have not specified a size, so you cannot use randNum[i] because those indices might not exist yet. I changed the declaration to:

uint[] public randNum = new uint[](5);

Take a look:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

contract Oracle {

    address public owner;
    uint public variableRand;

    constructor () {
        owner = msg.sender;
    }

    function setRandomVariable (uint _varRand) external {
        require (msg.sender == owner, "Not owner");
        variableRand = _varRand;
    }

}

contract Play {

    // Saves the value of what you have won in the session
    address owner;
    uint public currentPrize = 0;
    int[] public currentGuess;
    uint[] public randNum = new uint[](5);
    Oracle oracle;

    constructor(Oracle _oracle) {
        oracle = _oracle;
    }

    event Log(uint gas);

    function showPoolBalance() public view returns(uint) {
        return address(this).balance;
    }

    function randMod() public view returns(uint) {
        // grab information from the blockchain randomly to generate random numbers - we need something dynamically changing
        // abi.encodePacked concatonates arguments nicely
        return uint(keccak256(abi.encodePacked(oracle.variableRand(), block.timestamp, block.difficulty, msg.sender))) % 1000;
    }

    function inputGuess(int _guess) public {
        // Adds the guesses to the array
        currentGuess.push(_guess);
    }

    function clearGuesses() public {
        for (uint i = 0; i < currentGuess.length; i++) {
            //removes all of the guesses in the array
            currentGuess.pop();
        }
    }

    function abs(int x) private pure returns (int) {
        //returns the abolute value of the input
        return x >= 0 ? x : -x;
    }

    function game() private returns (int, uint) {

        for (uint i = 0; i <= 5; i++) {
            randNum[i] = randMod();
            int absv = abs(int(randNum[i]) - currentGuess[i]);
           if (absv < 6 && absv > 0) {
               currentPrize += 2;
           }
           else if (absv == 0) {
               currentPrize += 4;
           }
           else if (absv < 16 && absv > 5) {
               currentPrize += 1;
           }
           return (absv , randNum[i]);
        }

    }

    function play() public payable returns (int, uint) {

        require(msg.value == (10 ether), "You must pay with 10 ethers!");
        game();
    }
}

Besides that, I see that your code could really be improved in many areas.

For example, what is that return (absv , randNum[i]); doing inside the loop at the end of it and outside any if? The loop will only run once and then return. Maybe that is not what you want.

Take a look at this new implementation that might help you.

Take a look at playv2 and playv3, especially playv3, which saves a record SenderGuess into the senderGuesses mapping which can help you debug. It's an idea, you can modify it as you need.

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

contract Contract {

    event Payment(address indexed from, address indexed to, string recipientName);

    function sendPayment(address from, address payable to, string memory recipientName) public payable {
        to.transfer(msg.value);
        emit Payment(from, to, recipientName);
    }

}

contract Oracle {
    

    address public owner;
    uint public variableRand;

    constructor () {
        owner = msg.sender;
    }

    function setRandomVariable (uint _varRand) external {
        require (msg.sender == owner, "Not owner");
        variableRand = _varRand;
    }

}

contract Play {

    // Saves the value of what you have won in the session
    address owner;
    uint public currentPrize = 0;
    int[] public currentGuess;
    uint[] public randNum = new uint[](5);
    Oracle oracle;

    mapping(address => SenderGuess[]) public senderGuesses;

    struct SenderGuess {
        address sender;
        uint256 blockNumber;
        uint256[] guesses;
        uint256 diff;
        uint256 prize;
    }

    constructor(Oracle _oracle) {
        oracle = _oracle;
    }

    event Log(uint gas);

    function showPoolBalance() public view returns(uint) {
        return address(this).balance;
    }

    function randMod() public view returns(uint) {
        // grab information from the blockchain randomly to generate random numbers - we need something dynamically changing
        // abi.encodePacked concatonates arguments nicely
        return uint(keccak256(abi.encodePacked(oracle.variableRand(), block.timestamp, block.difficulty, msg.sender))) % 1000;
    }

    function inputGuess(int _guess) public {
        // Adds the guesses to the array
        currentGuess.push(_guess);
    }

    function clearGuesses() public {
        for (uint i = 0; i < currentGuess.length; i++) {
            //removes all of the guesses in the array
            currentGuess.pop();
        }
    }

    function abs(int x) private pure returns (int) {
        //returns the abolute value of the input
        return x >= 0 ? x : -x;
    }



    function game() private returns (int, uint) {

        for (uint i = 0; i <= 5; i++) {
            randNum[i] = randMod();
            int absv = abs(int(randNum[i]) - currentGuess[i]);
           if (absv < 6 && absv > 0) {
               currentPrize += 2;
           }
           else if (absv == 0) {
               currentPrize += 4;
           }
           else if (absv < 16 && absv > 5) {
               currentPrize += 1;
           }
           return (absv , randNum[i]);
        }

    }

    function play() public payable returns (int, uint) {

        require(msg.value == (10 ether), "You must pay with 10 ethers!");
        game();
    }

    // v2
    // This version gets closest guess and and assigns the prize multiple times.
    function playv2(uint256[] memory guesses) public payable returns (uint256) {
        require(msg.value == (10 ether), "You must pay with 10 ethers!");
        require(guesses.length == 5, "You need to send 5 guesses");

        uint256 _currentPrize = 10;

        // Getting the 'random' mod number only once because if the call to oracle.variableRand() does not return
        // a different "random" number, then the randMod function will return the same thing anyways.
        uint256 randNumber = randMod();

        for(uint256 i = 0; i < guesses.length; i++) {

            uint256 guess = guesses[i];
            uint256 min = randNumber > guess ? randNumber : guess;
            uint256 max = guess > randNumber ? guess : randNumber;

            uint256 diff = max - min;

            if (diff == 0) {
                _currentPrize += 4;
            } else if (diff < 6) {
               _currentPrize += 2;
           } else if (diff < 16) {
               _currentPrize += 1;
           }

        }

        return _currentPrize;

    }

    // v3
    // This version gets closest guess and and assigns the prize only once.
    function playv3(uint256[] memory guesses) public payable {
        require(msg.value == (10 ether), "You must pay with 10 ethers!");
        require(guesses.length == 5, "You need to send 5 guesses");

        uint256 _currentPrize = 10;

        // Getting the 'random' mod number only once because if the call to oracle.variableRand() does not return
        // a different "random" number, then the randMod function will return the same thing anyways.
        uint256 randNumber = randMod();

        uint256 smallestDiff = 16;

        for(uint256 i = 0; i < guesses.length; i++) {

            uint256 guess = guesses[i];
            uint256 min = randNumber > guess ? randNumber : guess;
            uint256 max = guess > randNumber ? guess : randNumber;

            uint256 diff = max - min;

            if(diff < smallestDiff) {
                smallestDiff = diff;
            }

        }

        if (smallestDiff == 0) {
            _currentPrize += 4;
        } else if (smallestDiff < 6) {
            _currentPrize += 2;
        } else if (smallestDiff < 16) {
            _currentPrize += 1;
        }

        SenderGuess memory senderGuess = SenderGuess(msg.sender, block.number, guesses, smallestDiff, _currentPrize);

        senderGuesses[msg.sender].push(senderGuess);

    }


}
3
  • 1
    Thanks a lot brother. The contract did actually work thanks to your suggestion. I used that return to print out the random number and guesses for the player to check their answers, which now seems so dumb to myself as well,do you think I should use event and emit for such purpose?!
    – Rjey
    Commented Sep 3, 2022 at 15:48
  • Yes. Or you could use hardhat console.log to debug. Commented Sep 3, 2022 at 16:01
  • Take a look at he update of my answer. I added a new play3 implementation example showing a new logic and saving a record of the result in a mapping so you can debug better. Commented Sep 3, 2022 at 18:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.