# Build your first fully on-chain game

By [Rickey](https://paragraph.com/@amenetwork) · 2023-12-03

---

The core principle of a fully on-chain game is to use contracts to implement game data and game logic. So how to implement the simplest a fully on-chain game? This tutorial does not use any development framework or game engine. You only need to know some of the simplest solidity codes.

### Entity Component System

ECS is a design pattern that improves code reusability by separating data from behavior. It is often used in game development. A minimal ECS consists of:

*   **Entity**: a unique identifier.
    
*   **Component**: a reusable data container attached to an entity.
    
*   **System**: the logic for operating entity components.
    
*   **World**: a container for an entity component system.
    

![ECS Workflow](https://storage.googleapis.com/papyrus_images/d0085bedac37ad536f40e7e4a06c1eed28c006bdef2d4093bb0449fbe6835682.png)

ECS Workflow

Implement minimal ECS with a few lines of solidity code

    contract World{
        uint256 entityId; //Entity
        mapping(uint256=>uint256) HPs; //Component
        function play()public{} //System
    }
    

### Start by building the world

In this tutorial we will build a magical world. You will play a warrior and a mage to fight against the monster boss. This is a very old-fashioned story and game mode.

    contract World{
    
    }
    

### Create characters in the game

Each character is an entity and has a unique number.

*   **Warrior**: entity id is 0
    
*   **Mage**: entity id is 1
    
*   **Monster**: entity id is 2
    

    uint256 warriorEntityId;
    uint256 mageEntityId;
    uint256 monsterEntityId;
    
    function createEntity() public {
       warriorEntityId = entityId; //create warrior, warriorEntityId=0
       entityId++;
       mageEntityId = entityId; //create mage, mageEntityId=1
       entityId++;
       monsterEntityId = entityId; //create monster, monsterEntityId=2
       entityId++;
    }
    

### Assign attributes to characters

The container of each attribute is called a component. There are four types of components in your world:

*   HP: represents the character’s health value
    
*   ATK: represents the character’s attack power
    
*   MP: represents the character’s mana
    
*   DEF: represents character’s defense
    

    mapping(uint256 => uint256) HPs;
    mapping(uint256 => uint256) ATKs;
    mapping(uint256 => uint256) MPs;
    mapping(uint256 => uint256) DEFs;
    

Different characters (entities) have different attributes (components), so you need to assign different components to different characters.

![Add components to entities](https://storage.googleapis.com/papyrus_images/aa7f36be7b2473374ffce1fe40ecde043a69ed66873ad5539d2069006d5b9860.png)

Add components to entities

    function addComponent() public {
      HPs[warriorEntityId] = 100;
      ATKs[warriorEntityId] = 20;
    
      HPs[mageEntityId] = 100;
      MPs[mageEntityId] = 30;
    
      HPs[monsterEntityId] = 100;
      ATKs[monsterEntityId] = 30;
      DEFs[monsterEntityId] = 5;
     }
    

### Add game logic

There are 4 steps in this game logic(system).

![Game logic](https://storage.googleapis.com/papyrus_images/8906f585d38aab0336d0ada8bae0d6fa20a4bf10e0201bf661e972d84b6d0fb0.png)

Game logic

The warrior attacks the monster, note that in order to enhance the fun of the game, we use `keccak256` to obtain a pseudo-random number, which is not a safe way.

            uint256 warriorRandomAttack = (uint256(
                keccak256(abi.encodePacked(block.timestamp, msg.sender))
            ) % (ATKs[warriorEntityId] - 10)) + 10;
            HPs[monsterEntityId] = (warriorRandomAttack - DEFs[monsterEntityId]) >
                HPs[monsterEntityId]
                ? 0
                : HPs[monsterEntityId] -
                    (warriorRandomAttack - DEFs[monsterEntityId]);
    

The monster attacks the warrior.

     uint256 monsterRandomAttack = (uint256(
                keccak256(
                    abi.encodePacked(
                        block.timestamp,
                        msg.sender,
                        warriorRandomAttack
                    )
                )
            ) % (ATKs[monsterEntityId] - 10)) + 10;
            HPs[warriorEntityId] = monsterRandomAttack > HPs[warriorEntityId]
                ? 0
                : (HPs[warriorEntityId] - monsterRandomAttack); 
    

Mage heals warrior.

            if (MPs[mageEntityId] > 0 && HPs[mageEntityId] > 0) {
                MPs[mageEntityId] -= 10;
                HPs[warriorEntityId] += 10;
            }
    

The monster attacks the Mage.

      HPs[mageEntityId] = monsterRandomAttack > HPs[mageEntityId]
                ? 0
                : (HPs[mageEntityId] - monsterRandomAttack);
    
            return (HPs[warriorEntityId], HPs[mageEntityId], HPs[monsterEntityId]);      
    

After running the play repeatedly, until one party's HP reaches 0.

    return (HPs[warriorEntityId], HPs[mageEntityId], HPs[monsterEntityId]);
    

### Package them together

    // SPDX-License-Identifier: CC0-1.0
    pragma solidity >=0.8.0;
    
    contract World {
        uint256 entityId;
        mapping(uint256 => uint256) HPs;
        mapping(uint256 => uint256) ATKs;
        mapping(uint256 => uint256) MPs;
        mapping(uint256 => uint256) DEFs;
        
        uint256 warriorEntityId;
        uint256 mageEntityId;
        uint256 monsterEntityId;
    
        function createEntity() public {
            warriorEntityId = entityId; //create warrior, warriorEntityId=0
            entityId++;
            mageEntityId = entityId; //create mage, mageEntityId=1
            entityId++;
            monsterEntityId = entityId; //create monster, monsterEntityId=2
            entityId++;
        }
    
        function addComponent() public {
            HPs[warriorEntityId] = 100;
            ATKs[warriorEntityId] = 20;
            HPs[mageEntityId] = 100;
            MPs[mageEntityId] = 30;
            HPs[monsterEntityId] = 100;
            ATKs[monsterEntityId] = 30;
            DEFs[monsterEntityId] = 5;
        }
    
        function play()
            public
            returns (
                uint256,
                uint256,
                uint256
            )
        {
            //warrior attacks monster
            uint256 warriorRandomAttack = (uint256(
                keccak256(abi.encodePacked(block.timestamp, msg.sender))
            ) % (ATKs[warriorEntityId] - 10)) + 10;
            HPs[monsterEntityId] = (warriorRandomAttack - DEFs[monsterEntityId]) >
                HPs[monsterEntityId]
                ? 0
                : HPs[monsterEntityId] -
                    (warriorRandomAttack - DEFs[monsterEntityId]);
    
            //monster attacks warrior
            uint256 monsterRandomAttack = (uint256(
                keccak256(
                    abi.encodePacked(
                        block.timestamp,
                        msg.sender,
                        warriorRandomAttack
                    )
                )
            ) % (ATKs[monsterEntityId] - 10)) + 10;
            HPs[warriorEntityId] = monsterRandomAttack > HPs[warriorEntityId]
                ? 0
                : (HPs[warriorEntityId] - monsterRandomAttack);
    
            //mage heals warrior
            if (MPs[mageEntityId] > 0 && HPs[mageEntityId] > 0) {
                MPs[mageEntityId] -= 10;
                HPs[warriorEntityId] += 10;
            }
    
            //monster attacks mage
            HPs[mageEntityId] = monsterRandomAttack > HPs[mageEntityId]
                ? 0
                : (HPs[mageEntityId] - monsterRandomAttack);
    
            return (HPs[warriorEntityId], HPs[mageEntityId], HPs[monsterEntityId]);
        }
    }
    

### Finale

Thank you for reading to the end. This is my first article about fully on-chain game. I hope you can understand FOCG through the simplest code. All codes are for testing only. If you have any questions you can contact me on [twitter](https://twitter.com/Web3Rickey). Although you don't use any game engine, you can still build your own world, but you must **keep it real**. Make sure your data and game logic are really on the chain.

If you are interested in AW and FOCG, these are two products I am building.

[https://awmap.xyz/](https://awmap.xyz/)

---

*Originally published on [Rickey](https://paragraph.com/@amenetwork/build-your-first-fully-on-chain-game)*
