Workshop - TCG

TCG is a multiplayer, multi-turn card game. This article will introduce the process of using ZK to implement it. At the same time, it will also introduce the design and implementation of web3.0 open games.

Step 0: Set up coding environment

$ npm init

$ npm i --save-dev hardhat
# "hardhat": "^2.22.3"

$ npx hardhat init
# ❯ Create a TypeScript project
#
# We are using TypeScript with Ethers.js here. you can choose whatever project structure you preferred.

Next, we will add the Shuffle SDK provided by Zypher, and the NFT interfaces provided by OpenZepplin.

$ npm i @zypher-game/secret-engine @openzeppelin/contracts
# "@openzeppelin/contracts": "^5.0.2"
# "@zypher-game/secret-engine": "^0.2.0"

The environment preparation is now complete.

Step 1: NFT & Player's Deck

Our goal is to use ERC-721 specification NFT to build our card game. First, we need to declare the NFT contracts:

Next, we will provide a basic interface for players to query, add, and remove their own decks. At this stage, we will not check the card holder and let the player edit it at will. We will confirm it when the game starts:

Finally, through basic testing, we ensure that this step functions as expected:

At this point, our Game contract allows players to edit the deck they use for battle.

If you want the game to have a more user-friendly editing interface, or even several backup decks, you are welcome to extend it and create more powerful functions!

Step 2: Validate Player's Deck and Start a Duel

After players get their decks ready, a battle begins.

Before joining a battle, we want to make sure the player's deck complies with our rules. In this example, our rules are that players must build a deck with a total of 20 cards, and each card must belong to the player:

In order to verify this function, we simply write an ERC-721 contract with batch mint function to facilitate our testing.

We first replace the blank card contract in the previous test with this NFT, and provide 50 cards each to two players at the beginning:

Then we can check whether the player's deck complies with the rules:

After ensuring that the functionality of our deck verification meets expectations, we can allow players to enter the game.

First, we first define a basic battle data structure, as well as its basic query and refresh methods:

We will first define a single group of battles here. Of course, you can rewrite it into more groups of battles later.

Next, we allow players to join the battle and make sure that their decks have been ready when they join:

Here we let the battle start automatically after the players arrive, and then we can test this part of the function:

At this point, our game can start and reset correctly, and ensure that players' hands comply with the rules when players join.

[!TIP] You may have noticed that according to the current design, players can trade cards to others after joining. To avoid this, More verifications can be added in the game, or after the player get the deck ready, the cards must be deposited to the game contract. These options will increase gas and player operation complexity. You can evaluate them and add them to your work appropriately.

Step 3: Shuffle Player's Deck

Now we're going to add Zypher Shuffle feature,

First, we first add the Verifiers corresponding interface and contract address provided by Zypher to our Game contract:

Before shuffling the cards, we must first collect the public keys of the two players and generate a aggregated key dedicated to the battle.

So let's slightly tweak the joinDuel method so that players can provide their own public key when joining the game:

At this time our test will fail due to lack of verifiers. In order to allow the game to run smoothly on the local side, we create a fixed set of keys and mocked verifiers for testing:

Then pass in the value of .pkxy in the previous joinDuel method:

At this point, our test can be successfully completed again, and after the game starts, we can obtain the game key necessary for next shuffling. Here we make a simple conversion to ensure that the game key text is converted to hex form. (Depending on the web3 library on the client side, the processing methods here will be somewhat different, our current environment is Ethers v6, so we use the toBeHex method to convert bigint to hex string)

The client is now ready to shuffle the cards, so we create some interfaces in the contract to allow players to submit the shuffled decks. In order to simplify the process, we let the first player encrypt the public cards and shuffle them directly, so the interface for shuffling his own deck and uploading it will be different from the next shuffles:

Above, we first wrote the part about shuffling the player's own deck for the first time, and then added a test to let two players shuffle their own decks and send them out:

Next, we add a method for shuffling the next deck into the contract to ensure no one can influence the ordering of cards:

It can be seen that it is basically the same as submitDeck in the previous step. The main difference is that we use the deck before shuffling to bring in the verification parameters to ensure that the next player actually uses the correct deck to shuffle the cards.

Then add the process of shuffling the other's cards to the test:

At this point, both players in the game have mixed up each other's decks and are ready to start playing cards.

Before playing the cards, we need to let the players know what cards they have in their hand. Please see the next chapter for the detailed process.

Step 4: Compute Reveal Tokens and Show Player's Hand Cards

Next, usually the player will hold some cards in his hand, the contents of which only the player can see.

In order to achieve this common card game function, we need to perform the following steps:

  1. Other players calculate and submit the reveal tokens of the player's card.

  2. After the player gets the reveal tokens of his card from the contract, he uses the secret engine to decrypt them locally. Player can see which cards in the NFT deck these cards are.

  3. When a player plays a card, in addition to telling the contract which card he played, he also sends the reveal token of the card at the same time. Allow other players to see the card content.

For this, we first adjust the contract structure to let each other know which cards they currently hold, and players need to calculate reveal tokens:

Then write some internal functions to verify reveal tokens and display the actual cards:

Next, we assume that the two players will draw three cards in their hands at the beginning, that is, 0, 1, and 2 cards in their decks.

We provide an interface in the contract for players to submit reveal tokens of each other's cards and verify them:

Then back in the test, we asked players to hand over each other's hand reveal tokens:

After the opponent submits the reveal tokens of the cards, we can review it.

Here we present the cards in the format of NFT ID. In your product, each NFT should have its own image and value:

After running the test again, you can already see the two players showing their cards at the end.

If it is a front-end interface, it should be able to convert these NFT IDs into better-looking cards and corresponding values.

Next, let's move on to the next chapter, which shows you how to play a card so that everyone can see its contents.

Step 5: Play a Card and Open Its Content

Now, both players can see their own cards, but the other player cannot see their contents.

Let’s adjust the contract and add a column to hold the cards played by the two players:

Then we add a new method for players to play cards. In addition to specifying the corresponding index of the hand, Also bring the reveal token and reveal proof so that we can decode it and store it in the board field.

Then we use the test to play the card and confirm that the results match:

At this point, we can correctly play cards and display them on the public card table in the form of NFT IDs.

In the future, we can add more card game logic to complete this card game.

Step 6: Add some gaming logic and have fun!

  • Let's start by adding an attack attribute to GameCard.sol

  • Back to Game.sol, let's give players some health

  • Then add some gaming logic when players play a card

There you have it, a card game on the blockchain with zk shuffle!

Last updated

Was this helpful?