Author: Magicblock; Source: MetaCat
This article shows an entity component system (ECS) using the Bolt engine ), which promotes the reusability of components and enables users to modify and extend the game's logic.
The framework significantly simplifies Solana-based development by abstracting underlying concepts such as Account Space and Program Derived Addresses. To compare with Anchor-based programs, see this tutorial?
https://book.anchor-lang.com/anchor_in_depth/milestone_project_tic-tac-toe .html
For a more detailed description of Bolt, please refer to the announcement blog post?
https: //blog.magicblock.gg/bolt-v0.1/
Developing Tic-Tac-Toe
The first part of this article details how to use the Bolt framework to implement game logic. The second part explains how to integrate a React-based client with the program, starting with an open-source Tic-Tac-Toe implementation.
The complete source code for this example is available here?
https:// github.com/magicblock-labs/bolt-tic-tac-toe
Game logic: using Bolt ECS to implement tic-tac-toe
First, install bolt-cli
:
npm install @magicblock-labs/bolt-cli
After installation, create a new project using the following command:
bolt init tic-tac-toe
Create Component
We need to define the required data structure. For simplicity, we will create two components: one containing the active players and another containing the grid information.
Create a new component using the following command:
bolt component players
This command creates a players component under program-ecs/components. A player component that holds the public keys of two players can be defined as follows:
use bolt_lang::*;declare_id!("5Xz6iiE2FZdpqrvCKbGqDajNYt1tP8cRGXrq3THSFo1q");#[component]#[derive(Default)]pub struct  ;Players { pub players: [Option<Pubkey>; 2],}
Second The component contains grid information. Create it using the following command:
bolt component grid
The grid component can be defined as:
use  ;bolt_lang::*;declare_id!("rdiVoU6KomhXBDMLi6UXVHvmjEUtKqb5iDCWChxMzZ7");#[component]pub& nbsp;struct Grid { pub board: [[Option<Sign& gt;; 3] ; 3], pub state: GameState, pub is_first_player_turn: bool,}#[component_deserialize]#[derive(PartialEq)] pub enum GameState { Active, Tie, Won { winner: Pubkey },}# [component_deserialize]#[derive(PartialEq)]pub enum Sign { ;pub fn from_usize(value: usize) -> Sign { match value { 0 => Sign:: _ => Sign::0, } ;{ fn default() -> Self { Self::new(GridInit{ board: [[None; 3]; 3], state: GameState::Active, ;true, }) }}
Create system (Systems)
The system implements game logic in a modular manner. They operate on a set of input components and can perform any calculation. Systems execute within your world instances and are subject to approval policies, for example, one world may allow anyone to submit new systems, while another world may require approval from a whitelisted party or DAO.
The first system we build will allow players to join a match:
bolt system join-game
Modify the logic (in program-ecs/systems/join-game.rs) to:
#[system]pub mod join_game {  & nbsp; & nbsp; & nbsp; pub & nbsp; fn & nbsp; execute (ctx: & nbsp; context & lt; components & gt;, & nbsp; _Args_P: & nbsp; VEC & LT; u8 & gt)) & nbsp;-& gt; & nbsp; result & lt; components & gt; & nbsp; {& nbsp; & nbsp idx = match players.iter_mut().position(|player| player.is_none()) {   ; Some(player_index) => player_index, None => return Err (PlayersError::GameFull.into()), }; ctx.accounts.players .players[idx] = Some(*ctx.accounts.authority.key); Ok(ctx.accounts) & nbsp;} & nbsp; & nbsp; & nbsp; & nbsp;#[System_input] & nbsp; & nbsp; & nbsp; pub & nbsp; components p; {& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; pub & nbsp; players: Players, }}
The second system implements the core logic of the game:
1. Create a game playing system:
bolt system play
2. Implementing logic:
Use & nbsp; bolt_lang ::*; use & nbsp; grid :: grid; use & nbsp; players :: Players; YHNGMJ5K5NQS3MHK65GFSIH ");#[System] PUB & Nbsp; Mod & Nbsp; Play & Nbsp; { pub fn execute(ctx: Context<Components>, args: Args) -> Result<Components> {   ; let grid = ;&mut ctx.accounts.players; let authority = *ctx.accounts.authority.key;   ; require!(players.players[0] == Some(authority) || players.players[1] == Some(authority) , TicTacToeError::NotInGame); require!(grid.state == grid::GameState::Active, TicTacToeError::NotActive ); let { 0 } else { 1 }; require!(grid.is_first_player_turn == (player_idx ==  ;0), TicTacToeError::NotPlayersTurn); //Core game logic match args { &nb sp; tile @ Args {   ; => match grid.board[tile.row as usize][tile.column as usize] {   ; Some(_) => return Err(TicTacToeError::TileAlreadySet.into()), None => grid.board[tile.row as usize][tile.column as usize] = Some(grid::Sign::from_usize( player_idx)); }   ; }, _ => return Err(TicTacToeError: :TileOutOfBounds.into()), } grid.is_first_player_turn = !grid .is_first_player_turn; check_winner (grid, authority); & nbsp; & nbsp; & nbsp; & nbsp;} & nbsp; & nbsp; & nbsp;#[System_input] & nbsp; & nbsp; & nbsp; RUCT & Nbsp; Components & Nbsp; {& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; pub grid: Grid, pub players: Players, }   ;#[arguments] struct Args { row: u8, column: u8, }}pub fn check_winner(grid: &mut Account<Grid>, player: Pubkey)  ;{ ...}
See full source code for details?
https://github.com/magicblock-labs/bolt-tic-tac-toe/blob/main/programs-ecs/systems/play/src/lib.rs
< p style="text-align: left;">As you noticed, the implementation is very simple. The tagged structure
system_input
defines the component input package that can be accessed and used in the
execute
function. Structures marked
arguments
define the arguments that your system can receive as input.
Build and test the program
Build the program using the following commands :
bolt build
This command compiles the program and automatically generates IDL and TypeScript types for client integration.
The process of setting up components and execution systems involves the following steps:
Instantiate a world.
Create a matching entity.
Attach the player and grid components to this matching entity.
Execute systems to facilitate gameplay.
TypeScript tests for the Tic-Tac-Toe game can be found here?
Connecting to the React client
Connecting to the React client is very simple, thanks to the dynamic retrieval and generation of types and the capabilities provided by the Bolt TypeScript SDK Utility functions.
Add dependencies:
yarn add -D @magicblock-labs/bolt-sdk
For example, to execute a system:
// Components
const GRID_COMPONENT = new PublicKey("rdiVoU6KomhXBDMLi6UXVHvmjEUtKqb5iDCWChxMzZ7");
const PLAYERS_COMPONENT = new PublicKey ("5Xz6iiE2FZdpqrvCKbGqDajNYt1tP8cRGXrq3THSFo1q");
// Systems
const JOIN_GAME = new PublicKey("2umhnxiCtmg5KTn4L9BLo24uLjb74gAh4tmpMLRKYndN");
const PLAY = new PublicKey("DyUy1naq1kb3r7HYBrTf7YhnGMJ5k5NqS3Mh k65GfSih");< /code>
const applySystem = await ApplySystem({
authority: publicKey,
system: JOIN_GAME,
entity,
components: [PLAYERS_COMPONENT],
});
const transaction = applySystem.transaction;
const signature = await submitTransaction(transaction);
Find a simple Tic-Tac-Toe UI made with React here?
https://github.com/magicblock-labs/bolt-tic-tac-toe/tree/main/app/react-tic-tac-toe
An important aspect to emphasize is thatonly IDs are needed to execute the system and instantiate components. This means that new logic and data structures can be created and utilized on the fly, allowing the development of mods and changing the behavior of the games.
Conclusion
We have walked through the use of Bolt ECS A simple implementation of the Tic-Tac-Toe game, demonstrating how to connect it to React UI. This highlights the simplicity and flexibility of the framework. In addition to abstracting Solana and reusing on-chain logic, we're excited about the possibilities BOLT will bring for user-generated logic and mod introduction. In subsequent examples, we will show how game developers can scale game logic independently and without permission, and how to use ephemeral rollups to achieve low latency/high throughput transactions.