# The Game of Zenet ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/zenet-banner.png) ## Table of Contents 1. [The original Senet game](#The-Original-Senet-Game) 2. [Zenet: The Modern Version](#Zenet-The-Modern-Version) 3. [Technology Used in the Project](#Technology-Used-in-the-Project) 1. [The Aleo Platform](#The-Aleo-Platform) 2. [The Unity Platform](#The-Unity-Platform) 4. [General Approach to the Problem](#General-Approach-to-the-Problem) 5. [Implementation Details](#Implementation-Details) 1. [Data Structures for Board Representation](#Data-Structures-for-Board-Representation) 2. [Records](#Records) 3. [Transitions](#Transitions) 6. [Records and Transitions Diagram](#Records-and-Transitions-Diagram) 7. [How to Build](#How-to-Build) 8. [Sample Gameplay](#Sample-Gameplay) 1. [Initializing the players](#1-Initializing-the-players) 2. [Player 1 creates a new Board](#2-Player-1-creates-a-new-Board) 3. [Player 1 makes their first move](#3-Player-1-makes-their-first-move) 4. [Player 2 makes their first move](#4-Player-2-makes-their-first-move) 5. [Player 2 makes an invalid move](#5-Player-2-makes-an-invalid-move) 6. [Player 2 amends their move](#6-Player-2-amends-their-move) 7. [Player 1 moves to a cell where there's an invisible piece from the opponent](#7-Player-1-moves-to-a-cell-where-there’s-an-invisible-piece-from-the-opponent) 8. [Player 1 moves again](#8-Player-1-moves-again) 9. [Frontend for the Game](#Frontend-for-the-Game) 10. [Workaround for the lack of testnet](#Workaround-for-the-lack-of-testnet) 10. [Challenges and lessons learned](#Challenges-and-lessons-learned) 11. [Contract Deployment to testnet3](#Contract-Deployment-to-testnet3) ## The Original Senet Game Senet is one of the oldest known board games, originating in ancient Egypt around 3100 BCE. It was a popular game among all social classes, from pharaohs to commoners. Senet boards have been found in tombs, including the tomb of Tutankhamun, indicating its cultural significance and association with the afterlife. It's a two-player game, played on a rectangular board with 30 squares arranged in three rows of ten. Each player has five pawns, which they move across the board according to the roll of casting sticks or dice. The objective is to be the first player to move all their pawns off the board, symbolizing the journey through the afterlife. Players must navigate through a series of special squares with unique properties or rules, representing various stages of the afterlife journey. Some squares allow players to move forward, some send players back, and some block movement entirely. If a player rolls a 1, 4, or 5 on the dice, they are granted an additional turn, which they can take immediately after completing their current turn. This continues until the player rolls either a 2 or 3, at which point the other player takes their turn. ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/ancient-senet-board.png) ## Zenet: The Modern Version In Zenet, there's an added element of mystery and magic, as pieces can become invisible after landing on the 15th tile, known as the House of Second Life. This new rule of invisibility increases the complexity and strategy of the game, as players must adapt their moves and tactics based on hidden information. To maintain privacy and ensure fair gameplay, Zenet uses zero-knowledge proofs to verify the legality of moves without revealing the actual positions of the invisible pieces. Using zero-knowledge proofs in Zenet adds an extra layer of intrigue and excitement, as players must constantly adapt to the hidden aspects of the game and make strategic decisions based on incomplete information. ### Some benefits of Using Zero-Knowledge Proofs in Zenet: - Privacy: Players' moves involving invisible pieces remain hidden, preserving the element of mystery and surprise throughout the game. - Security: Zero-knowledge proofs protect against cheating, ensuring that all moves are legal and follow the rules of the game without disclosing sensitive information. - Trust: The use of cryptographic techniques like zero-knowledge proofs establishes trust between players, as they can be confident that their opponent is not gaining an unfair advantage. ## Technology Used in the Project ### The Aleo Platform ![](https://assets-global.website-files.com/5e990b3c62a0fc1f2c268e7a/607e2e0ca878c72b82391be4_discover-aleo.png) The Aleo Platform is an innovative solution focused on providing a truly personal and private user experience on the web. Utilizing zero-knowledge cryptography and other privacy-preserving technologies, Aleo aims to create a future where web services are private-by-default, allowing users to retain control over their personal data and eliminating the need for service providers to store sensitive information. This paradigm shift has the potential to make the web more secure, compliant, and fair for all its users. For more information visit [https://www.aleo.org](https://www.aleo.org). ### The Unity Platform ![](https://unity.com/sites/default/files/styles/social_media_sharing/public/2022-02/U_Logo_White_CMYK.jpg?h=c66d06c9&itok=YQzbOLDK) The Unity Platform is a powerful and versatile game engine and development environment that enables creators to build immersive 2D and 3D games, as well as interactive experiences for various platforms. Widely recognized for its user-friendly interface, Unity offers a comprehensive suite of tools and features that streamline the development process, allowing developers to bring their creative visions to life with ease. With support for multiple platforms, including mobile, console, and PC, Unity has become a popular choice for both independent developers and larger studios seeking a flexible and efficient game development solution. For more information visit [https://unity.com](https://unity.com) ## General Approach to the Problem To implement Zenet, we first thoroughly studied the rules and mechanics of the original Senet game. Next, we identified the key features that we wanted to preserve in Zenet, such as the use of a board with 30 squares and the goal of moving pieces from one end of the board to the other. We also added new features to make the game more interesting and engaging, such as the ability to make pieces invisible and the use of a zero-knowledge proof system to enforce privacy regarding the invisible pieces. Once we had a clear idea of the game's rules and features, we began designing and implementing the game's code architecture. This involved breaking down the game into its various components, such as the game board, the pieces, and the game logic. We then implemented each component using the Leo programming language, along with a sample gameplay to showcase our implementation. In addition to the code development, we created a graphical interface for the game using the Unity platform. We designed a 3D scene where players can interact with a 3D Zenet board and roll the "knucklebones" (the equivalent of dice in Zenet). The Unity platform allowed us to create a visually appealing and immersive experience for players, bringing the game to life in a way that would be difficult to achieve with a simple 2D interface. ## Implementation Details ### Data Structures for Board Representation Zenet boards are represented by 32-bit unsigned integers. The first 30 bits represent the 30 cells in a Zenet board. We use two 32-bit integers: - `cell_state`: `0` means the cell is empty, `1` means the cell is occupied. - `cell_occ`: `0` means the occupant is Player 1, `1` means it's Player 2. When referring to individual cells, we use a 8-bit integer, with the following convention: - `0u8`: Empty cell - `1u8`: Player 1 - `2u8`: Player 2 For both `cell_state` and `cell_occ`, the less-significant bit (LSB) represents the first tile or cell, while the most-significant bit (MSB) represents the last one. The board's initial configuration is always the same: each player starts with five game pieces, which are placed alternately in the first ten cells of the board. So, the initial values for `cell_state` and `cell_occupant`, are as follows: - `cell_state` = `00000000000000000000001111111111` = `1032u32` - `cell_occ` = `00000000000000000000001010101010` = `682u32` ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/data-structures.png) ### Records The state of the game is kept on-chain with the help of two different records: - A `Board` record that represents a Zenet board. - An `InvisiblePieces` record, that represents the invisible pieces for each player. The ownership of the `Board` is determined by the player whose turn is next, this means that both players are aware of the positions of each other's visible pieces, but not their invisible pieces. For that reason, each player must keep track of their own invisible pieces. To ensure fairness and prevent cheating, the positions of these invisible pieces are kept private using an `InvisiblePieces` record for each player. ```javascript record Board { owner: address, gates: u64, cell_state: u32, cell_occ: u32, p1: address, p2: address } record InvisiblePieces { owner: address, gates: u64, positions: u32 } ``` ### Transitions There are two different transitions implemented in the game of Zenet: - `new()`: Creates a new `Board` and two `InvisiblePieces` records, one for each player. - `move()`: Consumes a `Board` record, an `InvisiblePieces` record, an origin cell and a dice roll and generates an updated `Board` record, along with an updated `InvisiblePieces` record for the current player. ```javascript transition new( opponent: address ) -> (Board, InvisiblePieces, InvisiblePieces) transition move( origin_cell: u8, dice: u8, board: Board, ip: InvisiblePieces ) -> (Board, InvisiblePieces) ``` ## Records and Transitions Diagram ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/zenet-diagram.png) ## How to Build To compile this Leo program, run: ```bash leo build ``` ## Sample Gameplay ### 1. Initializing the players In order to play Zenet, there must be two players with a single board. Players will be represented by their Aleo address. You can use the provided player accounts or generate your own. ``` Player 1: Private Key APrivateKey1zkpBUbGmWy43eKufUDiyHJc4VNMofLWBi1CgTq9GxoqNA3H View Key AViewKey1eN3KuiU4bHKDJo5yM3WXm9RZWkg9hBPxG6yyKe6rnKJh Address aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3 Player 2: Private Key APrivateKey1zkp7pHk17hq6cWoKQWJxAzHPwCupD6RECDjQZJk8t12BdEn View Key AViewKey1ouyBkb64s8m6BnBf1e6ibVUpL2Bx28mgVFLwD9ndbQK3 Address aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj ``` Save the keys and addresses. Set the `program.json` private_key and address to one of the newly created aleo accounts. We'll refer to this address as Player 1, and the remaining address as Player 2. ```json { "program": "zenet.aleo", "version": "0.0.0", "description": "", "development": { "private_key": "APrivateKey1zkpBUbGmWy43eKufUDiyHJc4VNMofLWBi1CgTq9GxoqNA3H", "view_key": "AViewKey1eN3KuiU4bHKDJo5yM3WXm9RZWkg9hBPxG6yyKe6rnKJh", "address": "aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3" }, "license": "MIT" } ``` ### 2. Player 1 creates a new Board. Player 1 executes the `new()` transition with Player 2's address as a parameter: **Run** ```bash export PLAYER2_ADDRESS=aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj leo run new $PLAYER2_ADDRESS ``` **Output** ```bash πŸš€ Executing 'zenet.aleo/new'... β€’ Executed 'new' (in 3596 ms) β›“ Constraints β€’ 'zenet.aleo/new' - 5,301 constraints (called 1 time) ➑️ Outputs β€’ { owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, cell_state: 1023u32.private, cell_occ: 682u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 7210918210391669260639851365122112056150882955833617507636357358503179831132group.public } β€’ { owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, positions: 0u32.private, _nonce: 310761922771395682817152413480995110831108802421135276464640650379690467960group.public } β€’ { owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, positions: 0u32.private, _nonce: 5063847865665357518111995648660364483466979801476404664891309029800811580865group.public } Leo βœ… Executed 'zenet.aleo/new' ``` There are three outputs here: the `Board` record, owned by Player 1, and two `InvisiblePieces` records, owned by Player 1 and Player 2, respectively. Notice that the values for `cell_state` and `cell_occ` in the `Board` record are `1023u32` and `682u32`, respectively. These correspond to the initial board arrangement (see [Implementation Details](#implementation-details) above). ### 3. Player 1 makes their first move Suppose that Player 1 rolls a 3 on the dice and wishes to move the game piece currently located on cell #9 to cell #12. This is the resulting board after this move is executed: ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/move-p1-9-3.png) **Run** ```bash leo run move 9u8 3u8 "{ owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, cell_state: 1023u32.private, cell_occ: 682u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 7890800848858615990577674120820649470995847806539552791604837931200094078541group.public }" "{ owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, positions: 0u32.private, _nonce: 1260228892496565700320197992505338749301641373211925149244050801561814298023group.public }" ``` **Output** ```bash πŸš€ Executing 'zenet.aleo/move'... β€’ Executed 'move' (in 7379 ms) β›“ Constraints β€’ 'zenet.aleo/move' - 55,450 constraints (called 1 time) ➑️ Outputs β€’ { owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, cell_state: 2815u32.private, cell_occ: 682u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 1409934603180638244566442530640648112189863997136401773590924503827538382529group.public } β€’ { owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, positions: 0u32.private, _nonce: 3342391348509252132638747362471767332582120143448787704921363163570949568794group.public } Leo βœ… Executed 'zenet.aleo/move' ``` After processing the turn, two records are produced. The first record is the updated `Board` record, which reflects the new positions of the pieces on the board. This record is owned by Player 2, as it is now their turn to play. The second record is an `InvisiblePieces` record, belonging to Player 1, who must keep the positions of their invisible pieces private. ### 4. Player 2 makes their first move It's now Player 2's turn to play. Suppose they roll a 2 on the dice and wish to move their piece to cell #12, but find that this cell is already occupied by a piece belonging to Player 1. Because the piece on cell #12 is unprotected, Player 2 may opt to exchange the positions of the two pieces, moving their own piece to cell #12 and Player 1's piece to cell #10. This move is legal according to the rules of the game and can be a strategic advantage for Player 2, as they are able to displace their opponent's piece and occupy a more favorable position on the board. This is the resulting board after this move is executed: ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/move-p2-10-2.png) We must run the program as Player 2, so let's switch the `program.json` file to use Player 2's keys: ```json { "program": "zenet.aleo", "version": "0.0.0", "description": "", "development": { "private_key": "APrivateKey1zkp7pHk17hq6cWoKQWJxAzHPwCupD6RECDjQZJk8t12BdEn", "view_key": "AViewKey1ouyBkb64s8m6BnBf1e6ibVUpL2Bx28mgVFLwD9ndbQK3", "address": "aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj" }, "license": "MIT" } ``` **Run** ```bash leo run move 10u8 2u8 "{ owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, cell_state: 2815u32.private, cell_occ: 682u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 1004757177813353300822404771547701633947006730478073655279174959646155183747group.public }" "{ owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, positions: 0u32.private, _nonce: 1464789014133543903436410348591640130929288960599214445445608713474398327142group.public }" ``` **Output** ```bash πŸš€ Executing 'zenet.aleo/move'... β€’ Executed 'move' (in 7733 ms) β›“ Constraints β€’ 'zenet.aleo/move' - 55,450 constraints (called 1 time) ➑️ Outputs β€’ { owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, cell_state: 2815u32.private, cell_occ: 2218u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 1039118141875097626328197633966104112998758183168296489726732103100390161461group.public } β€’ { owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, positions: 0u32.private, _nonce: 4374165097099060839041382712567463929743439389720416064428914179105778454589group.public } Leo βœ… Executed 'zenet.aleo/move' ``` Again, two records are produced. The first record is the updated `Board` record, belonging to Player 1, while the second one is an `InvisiblePieces` record, belonging to Player 2. ### 5. Player 1 moves Now it's Player 1's turn. Let's suppose they roll a 2 on the dice and wish to move their piece on cell #7 to cell #9. This is the resulting board after this move is executed: ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/move-p1-7-2.png) **Run** ```bash leo run move 7u8 2u8 "{ owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, cell_state: 2815u32.private, cell_occ: 2218u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 5863476156890724550843040600428608701027004669580939694903504655995771483413group.public }" "{ owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, positions: 0u32.private, _nonce: 1260228892496565700320197992505338749301641373211925149244050801561814298023group.public }" ``` **Output** ```bash πŸš€ Executing 'zenet.aleo/move'... β€’ Executed 'move' (in 7045 ms) β›“ Constraints β€’ 'zenet.aleo/move' - 59,181 constraints (called 1 time) ➑️ Outputs β€’ { owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, cell_state: 3007u32.private, cell_occ: 2218u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 5924825164096275999518163638519620290922331322518022937766238262108688003843group.public } β€’ { owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, positions: 0u32.private, _nonce: 3804515084251780732939555160317666989738405585247487127746964254480400932726group.public } Leo βœ… Executed 'zenet.aleo/move' ``` Once again, two records are produced. The first record is the updated `Board` record belonging to Player 2, while the second one is an `InvisiblePieces` record, belonging to Player 1. ### 6. Player 2 makes an invalid move After Player 1 completes their turn, it is now Player 2's turn to play. Player 2 rolls a 3 on the dice and decides to move their game piece from cell #6 to cell #9. However, this move is found to be invalid because Player 1's pieces in cells 9 and 10 are protected due to their adjacency (in the game, a piece is considered protected if it is adjacent to another piece belonging to the same player). This means that Player 2's attempted move is blocked and they must choose a different move that does not violate this rule. This rule adds an element of strategy and challenge to the game, as players must consider the positioning of their pieces on the board and plan their moves accordingly in order to gain an advantage over their opponents. ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/move-p2-6-3.png) **Run** ```bash leo run move 6u8 3u8 "{ owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, cell_state: 3007u32.private, cell_occ: 2218u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 6036362203879019831447420913205711606507044057841403559941745037099209138823group.public }" "{ owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, positions: 0u32.private, _nonce: 1464789014133543903436410348591640130929288960599214445445608713474398327142group.public }" ``` **Output** ```bash πŸš€ Executing 'zenet.aleo/move'... β€’ Executing 'zenet.aleo/move'... Error [ECLI0377010]: Failed to execute the `aleo run` command. SnarkVM Error: Failed to evaluate instruction (assert.eq r42 true ;): 'assert.eq' failed: 'false' is not equal to 'true' (should be equal) ``` ### 7. Player 2 amends their move After making an invalid move, Player 2 must attempt a different move using the same dice roll. In this instance, they opt to move their game piece from cell #12 to cell #15, which is a special cell known as the "House of Second Life". Pieces that land on this cell become invisible and their position is only known to the owner from that point onward. This is the resulting board after this move is executed: ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/move-p2-12-3.png) **Run** ```bash leo run move 12u8 3u8 "{ owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, cell_state: 3007u32.private, cell_occ: 2218u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 6036362203879019831447420913205711606507044057841403559941745037099209138823group.public }" "{ owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, positions: 0u32.private, _nonce: 1464789014133543903436410348591640130929288960599214445445608713474398327142group.public }" ``` **Output** ```bash πŸš€ Executing 'zenet.aleo/move'... β€’ Executing 'zenet.aleo/move'... β€’ Executed 'move' (in 7174 ms) β›“ Constraints β€’ 'zenet.aleo/move' - 59,181 constraints (called 1 time) ➑️ Outputs β€’ { owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, cell_state: 959u32.private, cell_occ: 170u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 6329222012009502731914383586062108794774340284056795796749360978658197690527group.public } β€’ { owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, positions: 16384u32.private, _nonce: 7111763228423710473885988355976691546381133536667901398255866537268004686626group.public } Leo βœ… Executed 'zenet.aleo/move' ``` After the last move, a new `InvisiblePieces` record is generated. This record contains information about the positions of Player 2's invisible pieces on the board. In this case, the record shows a value of `16384u32` for the `positions` field, which indicates that there is an invisible piece belonging to Player 2 on cell #15. Additionally, the `Board` record is updated to reflect the current state of the game. The `cell_state` and `cell_occ` fields indicate that there is no visible piece on cell #15, as it is currently occupied by the invisible piece belonging to Player 2. ### 8. Player 1 moves to a cell where there's an invisible piece from the opponent Let's now add some excitement to the game. Suppose Player 1 rolls a 5 on the dice and chooses to move their piece from cell #10 to cell #15. It's important to recall that an invisible piece belonging to Player 2 is already present on cell #15 from the last move. However, unless Player 1 has been attentive, they may have forgotten about this invisible piece. This is the resulting board after this move is executed: ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/move-p1-10-5.png) **Run** ```bash leo run move 10u8 5u8 "{ owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, cell_state: 959u32.private, cell_occ: 170u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 3971141806004630156214927014705437696910167048885340997783637456580646347901group.public }" "{ owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, positions: 0u32.private, _nonce: 2790178789406900946219076845702115358906487119661751887534651560275067222863group.public }" ```bash πŸš€ Executing 'zenet.aleo/move'... β€’ Executing 'zenet.aleo/move'... β€’ Executed 'move' (in 8106 ms) β›“ Constraints β€’ 'zenet.aleo/move' - 59,181 constraints (called 1 time) ➑️ Outputs β€’ { owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, cell_state: 447u32.private, cell_occ: 170u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 7769042611050190294974343034557510933207627529964258831433594565056569408217group.public } β€’ { owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, positions: 16384u32.private, _nonce: 5974742664547790682016444657430980112198406410772925275014173050789810161449group.public } Leo βœ… Executed 'zenet.aleo/move' ``` Notice that now that Player 1 has also invisible pieces, the `positions` field in the `InvisiblePieces` record is no longer `0u32`. ### 9. Player 1 moves again Player 1's turn has come around again, as they rolled a 5 in accordance with the rules of the game (see [The original Senet game](#the-original-senet-game) above). Suppose they now roll a 3 and choose to move their game piece from cell #15 to cell #18. This is the resulting board after this move is executed: ![](https://github.com/ZenetGame/ZenetAleo/raw/main/.resources/move-p1-15-3.png) **Run** ```bash leo run move 15u8 3u8 "{ owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, cell_state: 447u32.private, cell_occ: 170u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 6953544847073588504911484642740252760443414457882456614014267802834665825998group.public }" "{ owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, positions: 16384u32.private, _nonce: 5534725919850591397624943268698904780675877366552744519372352966937424067149group.public }" ```` **Output** ```bash πŸš€ Executing 'zenet.aleo/move'... β€’ Executing 'zenet.aleo/move'... β€’ Executed 'move' (in 7018 ms) β›“ Constraints β€’ 'zenet.aleo/move' - 59,181 constraints (called 1 time) ➑️ Outputs β€’ { owner: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, gates: 0u64.private, cell_state: 447u32.private, cell_occ: 170u32.private, p1: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, p2: aleo1qttvkxx3expu6ueqlwrydt7v0xjpqzmayeqsqlyha8d74u6q5srqfun7cj.private, _nonce: 549618835837952272690607585964045235474523446997989471415578705992395385220group.public } β€’ { owner: aleo172xc22y5jfzun0ezw7yfc55203sy6ufdvcugysqdw2ajpw86uc9q57enk3.private, gates: 0u64.private, positions: 131072u32.private, _nonce: 5514577450851620812996702835257166272839993045599329622088981949580879377389group.public } Leo βœ… Executed 'zenet.aleo/move' ``` Please keep in mind the following points: - The `cell_state` and `cell_occ` fields in the `Board` record remain unchanged after the piece is moved. This is because the moved piece is invisible and has no visual impact on the board. - The `positions` field in the `InvisiblePieces` record now displays the value `131072u32`, indicating the presence of an invisible piece in cell #18. This value is computed as 2^(18-1). ## Frontend for the Game We chose Unity as the development platform for the user interface of Zenet because it provided us with a powerful and flexible set of tools for creating engaging and immersive 3D experiences. In particular, Unity allowed us to abstract away much of the complexity of game development, enabling us to focus on creating a visually appealing and intuitive interface for our players. By leveraging Unity's built-in features and capabilities, we were able to create a 3D scene that accurately represented the game board and its pieces, as well as to implement animations and other visual effects that added to the game's overall appeal. Here is a screenshot of the user interface: ![](https://i.imgur.com/ZrDSFy3.jpg) ## Challenges and lessons learned One of the main challenges we faced during the development of Zenet was understanding the record-oriented paradigm of Aleo. This required us to rethink our approach to programming and data management, but once we grasped the concept, everything made more sense. However, the most significant challenge we encountered was dealing with Aleo's testnet3, which was down for almost the entire last two weeks of the Hackathon, up until the deadline. This prevented us from testing our application on a live testnet, which was essential for ensuring that the game worked as intended on the blockchain. We also found it challenging to run a local node due to the software's complexity and high computational resource requirements. However, as finalists, we were given more time to complete the submission and we were able to deploy our contract to testnet3. ## Contract Deployment to testnet3 After the testnet came up again, we were able to deploy our contract to testnet3 by executing the following SnarkOS command: ```bash snarkos developer deploy "zenet.aleo" --private-key "${PRIVATEKEY}" --query "https://vm.aleo.org/api" --path "${PATHTOAPP}/build/" --broadcast "https://vm.aleo.org/api/testnet3/transaction/broadcast" --fee 25000000 --record "${RECORD}" πŸ“¦ Creating deployment transaction for 'zenet.aleo'... βœ… Successfully deployed 'zenet.aleo' to https://vm.aleo.org/api/testnet3/transaction/broadcast. at1er0kvnamp05sdyttktkckawz52h0aktnxjmwrj30g0v0el4xfcysv68gg5 ``` Following is the corresponding deployment transaction: https://vm.aleo.org/api/testnet3/transaction/at1er0kvnamp05sdyttktkckawz52h0aktnxjmwrj30g0v0el4xfcysv68gg5