# Hands On Game Using VRF

<figure><img src="/files/WQ2Fa6bSdnaJsXg7ighd" alt=""><figcaption></figcaption></figure>

#### Deploying Our Game in Remix

For this demo, we will be using Remix IDE. Make sure you've [completed Section 2 ](/bootcamp-2024/2.-smart-contract-and-solidity-fundamentals.md)and [Section 3 ](/bootcamp-2024/6.-nfts-and-chainlink-automation.md)before attempting this exercise.

{% embed url="<https://remix.ethereum.org/>" %}

First create a file called **Runners.sol** and paste the following code (make sure to adjust your Remix compiler to refer to the compiler version in the pragma statement below):

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

// Deploy this contract on Fuji

import "@openzeppelin/contracts@4.6.0/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@4.6.0/utils/Counters.sol";
import "@openzeppelin/contracts@4.6.0/utils/Base64.sol";

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";

contract Runners is ERC721, ERC721URIStorage, VRFConsumerBaseV2  {
    using Counters for Counters.Counter;
    using Strings for uint256;

    // VRF
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(uint256 requestId, uint256[] randomWords);

    struct RequestStatus {
        bool fulfilled; // whether the request has been successfully fulfilled
        bool exists; // whether a requestId exists
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus) public s_requests; /* requestId --> requestStatus */          

    // Fuji coordinator
    // https://docs.chain.link/docs/vrf/v2/subscription/supported-networks/
    VRFCoordinatorV2Interface COORDINATOR;
    address vrfCoordinator = 0x2eD832Ba664535e5886b75D64C46EB9a228C2610;
    bytes32 keyHash = 0x354d2f95da55398f44b7cff77da56283d9c6c829a4bdf1bbcaf2ad6a4d081f61;
    uint32 callbackGasLimit = 2500000;
    uint16 requestConfirmations = 3;
    uint32 numWords =  1;

    // past requests Ids.
    uint256[] public requestIds;
    uint256 public lastRequestId;
    uint256[] public lastRandomWords;

    // Your subscription ID.
    uint64 public s_subscriptionId;

    //Runners NFT
    Counters.Counter public tokenIdCounter;
    string[] characters_image = [
        "https://ipfs.io/ipfs/QmTgqnhFBMkfT9s8PHKcdXBn1f5bG3Q5hmBaR4U6hoTvb1?filename=Chainlink_Elf.png",
        "https://ipfs.io/ipfs/QmZGQA92ri1jfzSu61JRaNQXYg1bLuM7p8YT83DzFA2KLH?filename=Chainlink_Knight.png",
        "https://ipfs.io/ipfs/QmW1toapYs7M29rzLXTENn3pbvwe8ioikX1PwzACzjfdHP?filename=Chainlink_Orc.png",
        "https://ipfs.io/ipfs/QmPMwQtFpEdKrUjpQJfoTeZS1aVSeuJT6Mof7uV29AcUpF?filename=Chainlink_Witch.png"
    ];
    string[] characters_name = [
        "Elf",
        "Knight",
        "Orc",
        "Witch"
    ];

    struct Runner {
        string name;
        string image;
        uint256 distance;
        uint256 round;
    }
    Runner[] public runners;
    mapping(uint256 => uint256) public request_runner; /* requestId --> tokenId*/


    constructor(uint64 subscriptionId) ERC721("Runners", "RUN")
        VRFConsumerBaseV2(vrfCoordinator)        
    {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        s_subscriptionId = subscriptionId;
        safeMint(msg.sender,0);
    }

    function safeMint(address to, uint256 charId) public {
        uint8 aux = uint8 (charId);
        require( (aux >= 0) && (aux <= 3), "invalid charId");
        string memory yourCharacterImage = characters_image[charId];

        runners.push(Runner(characters_name[charId], yourCharacterImage, 0, 0));

        uint256 tokenId = tokenIdCounter.current();
        string memory uri = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "', runners[tokenId].name, '",'
                        '"description": "Chainlink runner",',
                        '"image": "', runners[tokenId].image, '",'
                        '"attributes": [',
                            '{"trait_type": "distance",',
                            '"value": ', runners[tokenId].distance.toString(),'}',
                            ',{"trait_type": "round",',
                            '"value": ', runners[tokenId].round.toString(),'}',
                        ']}'
                    )
                )
            )
        );
        // Create token URI
        string memory finalTokenURI = string(
            abi.encodePacked("data:application/json;base64,", uri)
        );
        tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, finalTokenURI);
    }


    function run(uint256 tokenId) external returns (uint256 requestId) {

        require (tokenId < tokenIdCounter.current(), "tokenId not exists");

        // Will revert if subscription is not set and funded.
        requestId = COORDINATOR.requestRandomWords(
            keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            randomWords: new uint256[](0),
            exists: true,
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        request_runner[requestId] = tokenId;
        return requestId;      
    }

    function fulfillRandomWords(
        uint256 _requestId, /* requestId */
        uint256[] memory _randomWords
    ) internal override {
        require (tokenIdCounter.current() >= 0, "You must mint a NFT");
        require(s_requests[_requestId].exists, "request not found");
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        lastRandomWords = _randomWords;

        uint aux = (lastRandomWords[0] % 10 + 1) * 10;
        uint256 tokenId = request_runner[_requestId];
        runners[tokenId].distance += aux;
        runners[tokenId].round ++;

        string memory uri = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "', runners[tokenId].name, '",'
                        '"description": "Chainlink runner",',
                        '"image": "', runners[tokenId].image, '",'
                        '"attributes": [',
                            '{"trait_type": "distance",',
                            '"value": ', runners[tokenId].distance.toString(),'}',
                            ',{"trait_type": "round",',
                            '"value": ', runners[tokenId].round.toString(),'}',
                        ']}'
                    )
                )
            )
        );
        // Create token URI
        string memory finalTokenURI = string(
            abi.encodePacked("data:application/json;base64,", uri)
        );
        _setTokenURI(tokenId, finalTokenURI);
    }

    function getRequestStatus(
        uint256 _requestId
    ) external view returns (bool fulfilled, uint256[] memory randomWords) {

    }

    // The following functions are overrides required by Solidity.

    function tokenURI(uint256 tokenId)
        public view override(ERC721, ERC721URIStorage) returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage)
    {
        super._burn(tokenId);
    }
}
```

In our constructor, you can see we are passing in a **subscriptionId** which means we first need to set up our VRF subscription here:

{% embed url="<https://vrf.chain.link/>" %}

A VRF Subscription is a way to ensure that the decentralized Chainlink VRF service that sends your smart contract verifiably random numbers is "funded" with LINK.

For this exercise, let's use the Avalanche Fuji testnet. &#x20;

First ensure you are connected to **Avalanche Fuji** network in your wallet. Then select **Create Subscription.** This subscription will be used to manage your VRF service for your smart contract.

<figure><img src="/files/zmZXuy2VCimYeE9hUdh9" alt=""><figcaption><p>Select 'Create Subscription'</p></figcaption></figure>

On the **Create Subscription** page, enter a name for your subscription (optional) and click **create subscription** again.

<figure><img src="/files/y9A1YWIvDefcuQ5Xfyty" alt=""><figcaption><p>Select 'Create subscription'</p></figcaption></figure>

Then you will **sign** and **confirm** the following metamask transactions. After you will see the following pop-up on your subscription page. You must wait for the transaction to be confirmed for a few seconds, after which you will see **add funds** which you must select.

<figure><img src="/files/6HO4SYuy1h5H3oKWHgeJ" alt=""><figcaption><p>Select 'Add Funds'</p></figcaption></figure>

After you click add funds you will be prompted to enter an amount of **LINK** tokens (if you need more testnet LINK tokens and AVAX tokens, head to <https://faucets.chain.link/fuji>).&#x20;

For this demo we will be adding **10 LINK**, then select **confirm** and confirm the following transaction.

<figure><img src="/files/eMjFj84XOyjrDplQXOE3" alt=""><figcaption><p>Select 'Confirm'</p></figcaption></figure>

Now we will be directed to the subscription page. From this page we need to copy our **ID** which is our **subscriptionId**.

<figure><img src="/files/PrHwAv4gQzr8DjcAif4o" alt=""><figcaption></figcaption></figure>

Now we want to head back to our remix to the **Deploy and run transactions** tab, select environment **Injected Provider - Metamask** and make sure we are still on Avalanche Fuji Network (43113).&#x20;

Then with our copied subscriptionId we will deploy the **Runners.sol** contract, then select the orange **Deploy** button

<figure><img src="/files/02NN4coM47r51JQS8fJK" alt=""><figcaption><p>Paste your subcriptionId and select 'Deploy'</p></figcaption></figure>

In our constructor we minted an NFT, so now we will go to [**Opensea**](https://testnets.opensea.io/) and have a look at this NFT. To do this, you must copy your contract address. Note. Opensea does not support all testets, but it does support Avalanche Fuji. Here is a [list of  testnets that Opensea supports](https://docs.opensea.io/reference/supported-chains#testnets).&#x20;

{% embed url="<https://testnets.opensea.io/>" %}

Next, paste your contract address in the Openseas search bar and select your collection. You should now see an NFT minted which is the **Elf**.

<figure><img src="/files/WHsqFhK1FJOb8pKqn9PR" alt=""><figcaption></figcaption></figure>

We now want to take our copied contract address, and head back to our VRF Subscription page. On this page we need to select **Add Consumer**.&#x20;

<figure><img src="/files/6TGuSDze4tk5bqtaDPPj" alt=""><figcaption><p>Select 'Add consumer'</p></figcaption></figure>

This will prompt you to enter you consumer address, where you will paste your contract address and click **add consumer.**

<figure><img src="/files/xkYpbaIp8ZXgBgaLH1UK" alt=""><figcaption><p>Select 'Add consumer'</p></figcaption></figure>

Once this transaction is confirmed, we will head back to our contract in remix, open it, and call the orange **run** function passing in '0' for the parameter **tokenId**. Then click **transact** to execute this function.

<figure><img src="/files/XMElcq0hbM2uwzeAzPmj" alt=""><figcaption><p>Pass 0 as a parameter for 'tokenId' in the 'run' function then select transact</p></figcaption></figure>

Once the transaction has been executed, scroll down until you see the blue **lastRequestId** function and select this. This will return a long number which is the **requestId** for your call to VRF so copy this.

<figure><img src="/files/f8BXalmUwyHSeR1FsZZg" alt=""><figcaption></figcaption></figure>

After some time has passed we will then paste this requestId into the blue **s\_requests** function and we should expect to see the following as **true**

<figure><img src="/files/jJ0XnTElagcFAdYkGtju" alt=""><figcaption><p>Both 'fulfilled' and 'exists' are 'true'</p></figcaption></figure>

Now we will call the blue **runners** function, passing '0' as a parameter

<figure><img src="/files/c6VmY0Atkvhq2jXf1LZ6" alt=""><figcaption></figcaption></figure>

You will notice that your value for **distance** may be different, mine was '50'. This is because Chainlink VRF was used to generate this number randomly - we cannot predict it or know it in advance! Random!

Lastly we will head back to Opensea and view the previously minted NFT. If we scroll down and open the **Levels** tab you will see that the NFT metadata has been updated with this change!

<figure><img src="/files/XhGoiii1BmGAb4eGftUn" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://cll-devrel.gitbook.io/bootcamp-2024/8.-random-numbers-with-chainlink-vrf/hands-on-game-using-vrf.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
