本文展示了一个使用 Bolt 引擎的实体组件系统 (ECS) 开发的简单游戏示例,它促进了组件的可重用性,并使用户能够修改和扩展游戏的逻辑。
该框架通过抽象帐户空间(Account Space)和程序派生地址 (Program Derived Addresses)等底层概念,显著简化了基于 Solana 的开发。要与基于 Anchor 的程序进行比较,请参阅本教程👇
https://book.anchor-lang.com/anchor_in_depth/milestone_project_tic-tac-toe.html
有关 Bolt 的更详细说明,请参阅公告博文👇
https://blog.magicblock.gg/bolt-v0.1/
开发井字棋
本文第一部分详细介绍如何使用 Bolt 框架实现游戏逻辑。第二部分解释从开源井字棋实现开始,如何将基于 React 的客户端与程序集成。
该示例的完整源代码可在此处获取👇
https://github.com/magicblock-labs/bolt-tic-tac-toe
游戏逻辑:使用 Bolt ECS 实现井字棋
首先,安装 bolt-cli
:
npm install @magicblock-labs/bolt-cli
安装后,使用以下命令创建新项目:
bolt init tic-tac-toe
创建组件(Component)
我们需要定义所需的数据结构。为简单起见,我们将创建两个组件:一个包含活跃玩家,另一个包含网格信息。
使用以下命令创建新组件:
bolt component players
此命令在 program-ecs/components 下创建一个players 组件。保存两个玩家的公钥的玩家组件可以定义如下:
use bolt_lang::*;
declare_id!("5Xz6iiE2FZdpqrvCKbGqDajNYt1tP8cRGXrq3THSFo1q");
#[component]
#[derive(Default)]
pub struct Players {
pub players: [Option
; 2], }
第二个组件包含网格信息。使用如下命令创建它:
bolt component grid
网格组件可以定义为:
use bolt_lang::*;
declare_id!("rdiVoU6KomhXBDMLi6UXVHvmjEUtKqb5iDCWChxMzZ7");
#[component]
pub struct Grid {
pub board: [[Option
; 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 {
X,
O,
}
impl Sign {
pub fn from_usize(value: usize) -> Sign {
match value {
0 => Sign::X,
_ => Sign::O,
}
}
}
impl Default for Grid {
fn default() -> Self {
Self::new(GridInit{
board: [[None; 3]; 3],
state: GameState::Active,
is_first_player_turn: true,
})
}
}
创建系统(Systems)
系统以模块化方式实现游戏逻辑。它们对一组输入组件进行操作,并且可以执行任何计算。系统在你的世界实例中执行,并遵守审批策略,例如,一个世界可以允许任何人提交新系统,而另一个世界可能需要白名单方或 DAO 的批准。
我们构建的第一个系统将允许玩家加入比赛:
bolt system join-game
将逻辑(在 program-ecs/systems/join-game.rs 中)修改为:
#[system]
pub mod join_game {
pub fn execute(ctx: Context
, _args_p: Vec ) -> Result { let players = &mut ctx.accounts.players.players;
let idx = match players.iter_mut().position(|player| player.is_none()) {
> player_index, =
None => return Err(PlayersError::GameFull.into()),
};
Some(*ctx.accounts.authority.key); =
Ok(ctx.accounts)
}
#[system_input]
pub struct Components {
pub players: Players,
}
}
第二个系统实现了游戏的核心逻辑:
1. 创建一个玩游戏系统:
bolt system play
2. 实现逻辑:
use bolt_lang::*;
use grid::Grid;
use players::Players;
declare_id!("DyUy1naq1kb3r7HYBrTf7YhnGMJ5k5NqS3Mhk65GfSih");
#[system]
pub mod play {
pub fn execute(ctx: Context
, args: Args) -> Result { let grid = &mut ctx.accounts.grid;
let players = &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 player_idx : usize = if players.players[0] == Some(authority) { 0 } else { 1 };
require!(grid.is_first_player_turn == (player_idx == 0), TicTacToeError::NotPlayersTurn);
// Core game logic
match args {
tile @ Args {
row: 0..=2,
column: 0..=2,
} => 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);
Ok(ctx.accounts)
}
#[system_input]
pub struct Components {
pub grid: Grid,
pub players: Players,
}
#[arguments]
struct Args {
row: u8,
column: u8,
}
}
pub fn check_winner(grid: &mut Account
, player: Pubkey) { ...
}
有关详细信息,请参阅完整源代码👇
https://github.com/magicblock-labs/bolt-tic-tac-toe/blob/main/programs-ecs/systems/play/src/lib.rs
正如你所注意到的,实现非常简单。标记的结构体 system_input
定义了可以在 execute
函数中访问和使用的组件输入包。标记为的结构体 arguments
定义你的系统可以作为输入接收的参数。
构建并测试程序
使用以下命令构建程序:
bolt build
此命令编译程序并自动生成 IDL 和 TypeScript 类型以进行客户端集成。
设置组件和执行系统的过程涉及以下步骤:
实例化一个世界。
创建与之匹配实体。
将玩家和网格组件附加到该匹配实体。
执行系统以促进游戏玩法。
Tic-Tac-Toe 游戏的 TypeScript 测试可以在这里找到👇
连接 React 客户端
连接 React 客户端非常简单,这要归功于类型的动态检索和生成以及 Bolt TypeScript SDK 提供的实用函数。
添加依赖项:
yarn add -D @magicblock-labs/bolt-sdk
例如,要执行一个系统:
// Components
const GRID_COMPONENT = new PublicKey("rdiVoU6KomhXBDMLi6UXVHvmjEUtKqb5iDCWChxMzZ7");
const PLAYERS_COMPONENT = new PublicKey("5Xz6iiE2FZdpqrvCKbGqDajNYt1tP8cRGXrq3THSFo1q");
// Systems
const JOIN_GAME = new PublicKey("2umhnxiCtmg5KTn4L9BLo24uLjb74gAh4tmpMLRKYndN");
const PLAY = new PublicKey("DyUy1naq1kb3r7HYBrTf7YhnGMJ5k5NqS3Mhk65GfSih");
const applySystem = await ApplySystem({
authority: publicKey,
system: JOIN_GAME,
entity,
components: [PLAYERS_COMPONENT],
});
const transaction = applySystem.transaction;
const signature = await submitTransaction(transaction);
在这里找到用 React 制作的简单井字棋 UI👇
https://github.com/magicblock-labs/bolt-tic-tac-toe/tree/main/app/react-tic-tac-toe
需要强调的一个重要方面是:执行系统和实例化组件仅需要 ID。这意味着可以动态创建和利用新的逻辑和数据结构,从而能够开发模组并更改游戏的行为。
结论
我们已经演练了使用 Bolt ECS 的井字棋游戏的简单实现,演示了如何将其连接到 React UI。这凸显了该框架的简单性和灵活性。除了抽象 Solana 和重用链上逻辑之外,我们对 BOLT 将为用户生成的逻辑和 mod 引入带来的可能性感到兴奋。在后续示例中,我们将展示游戏开发人员如何独立且无需许可地扩展游戏逻辑,以及如何使用临时汇总(Ephemeral Rollups)实现低延迟/高吞吐量交易。
原文链接:https://blog.magicblock.gg/bolt-tic-tac-toe/