> For the complete documentation index, see [llms.txt](https://cll-devrel.gitbook.io/bootcamp-2024/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cll-devrel.gitbook.io/bootcamp-2024/8.-random-numbers-with-chainlink-vrf/hands-on-game-using-vrf.md).

# 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>
