Customized Node
A customized zk & room for game
Contents
In z4 engine, developers only need to implement an interface and configure some startup parameters, and then will get a complete z4 game node.
z4 new --name my-game
will generate new Z4 game template.
Handler
Handler
trait is the core interface. Developers can implement the game logic in it, and then they can directly use the z4 engine to run it. This interface including chain_accept
room events from the chain, chain_create
room events when the room is accepted by this node, and also supporting events when nodes go online and offline (connected and disconnected with websocket), and finally how to handle user request events in general.
pub trait Handler: Send + Sized + 'static {
type Param: Param;
/// Viewable for game
/// If true, when send message to all will also send to viewers
/// If set false, no viwers
fn viewable() -> bool;
/// accept params when submit to chain
async fn chain_accept(peers: &[Player]) -> Vec<u8>;
/// create new room scan from chain
async fn chain_create(peers: &[Player], params: Vec<u8>, rid: RoomId, seed: [u8; 32]) -> Option<(Self, Tasks<Self>)>;
/// New Viewer online if viewable is true
async fn viewer_online(&mut self, _peer: PeerId) -> Result<HandleResult<Self::Param>> {
Ok(HandleResult::default())
}
/// New Viewer offline if viewable is true
async fn viewer_offline(&mut self, _peer: PeerId) -> Result<HandleResult<Self::Param>> {
Ok(HandleResult::default())
}
/// when player online
async fn online(&mut self, _player: PeerId) -> Result<HandleResult<Self::Param>> {
Ok(HandleResult::default())
}
/// when player offline
async fn offline(&mut self, _player: PeerId) -> Result<HandleResult<Self::Param>> {
Ok(HandleResult::default())
}
/// handle message in a room
async fn handle(&mut self, player: PeerId, param: Self::Param) -> Result<HandleResult<Self::Param>>;
/// Generate proof for this game result, when find game is over
async fn prove(&mut self) -> Result<(Vec<u8>, Vec<u8>)>;
}
Let's explain the parameters and methods one by one.
Param
Firstly, type Param: Param
, this method defines the type which the data in the network enters the handle
. The specific Param
trait is defined as follows:
/// serialize & deserialize for params
pub trait Param: Sized + Send + Default {
/// To json value
fn to_string(&self) -> String;
/// From json value
fn from_string(s: String) -> Result<Self>;
/// To bytes
fn to_bytes(&self) -> Vec<u8>;
/// From bytes
fn from_bytes(bytes: Vec<u8>) -> Result<Self>;
/// To json value
fn to_value(&self) -> Value;
/// From json value
fn from_value(v: Value) -> Result<Self>;
}
It defines how to convert to and from serde_json::Value
, and also how to convert to and from Vec<u8>
and String
. We provide some default params, as follows:
pub struct MethodValues { method: String, params: Vec<Value> };
impl Param for Vec<u8>;
impl Param for String;
impl Param for Value;
If developers don't want to define it specially, can directly use the MethodValues
structure.
chain_accept
When z4 node finds that a room on the chain is in an acceptable state, it will actively call the accept method and pass in the players information as a parameter. The return value is what is carried when the accept transaction is submitted to the chain. The content of this information is completely defined by the developer based on game logic, and can also be empty.
The player's information includes: the player's eth address, the player's locally generated temporary account address and the public key of the temporary account.
chain_create
When the z4 node finds that it has successfully accepted a room on the chain, it will call the create method to create the room. Parameters include: room players information, data submitted when accepting, room id, and the return value is the room structure and the scheduled task for the room. Regarding the room definition tasks, the following will explain in detail how to define scheduled tasks.
online and offline
When z4 node discovers that a player has connected, it will call the online method. Developers can handle the logic of the player's first online connection or reconnection, such as synchronizing the latest status of the room, notifying others that the player is online, etc.
When z4 node finds that a player has disconnected, it will call the offline method. Developers can handle the player's disconnection status.
handle
When the z4 node receives a specific interaction request from the player, it will call the handle method. Developers can specifically define the game processing logic corresponding to different methods.
It should be noted that online, offline, and handle all using the temporary account generated locally by the player, not the player's eth account.
The interaction between the player and z4 node adopts the jsonrpc format. Therefore, the parameters are method and param, and the return value is also two method and param. z4 will help handle others.
Tasks
Game will have some scheduled tasks, including countdown and recurring tasks, can be defined as Task
and added to the z4 engine. The z4 engine will automatically execute various scheduled tasks in the game.
pub trait Task: Send + Sync {
type H: Handler;
fn timer(&self) -> u64;
async fn run(&mut self, state: &mut Self::H) -> Result<HandleResult<<Self::H as Handler>::Param>>;
}
Developers need to specify Handler
, which is the core interface defined above. Each timer is set, which supports both fixed numbers and numbers that change according to status. The core run
structure determines how to run the scheduled task. This task will affect the state of the core structure Handler
and return a general HandleResult
.
We also encapsulate a more friendly format
pub type Tasks<H> = Vec<Box<dyn Task<H = H>>>;
Therefore, if there are no scheduled tasks in the game, developers can directly give an empty array.
HandleResult
This is a common return value of z4 engine. Z4 will perform different network transfers and status processing according to different fields in the return value, including sending messages to all players in the current room and sending messages to a specific player in the room. When the game ends, subsequent certification and on-chain operations are automatically performed.
This is a common return value of z4 engine. Z4 will perform different network transmission and status processing according to different fields in the return value, including sending messages to all players in the current room and sending messages to a specific player in the room. When the game over, will execute the verify and submit result to chain automatically.
pub struct HandleResult<P: Param> {
/// need broadcast the msg in the room
all: Vec<P>,
/// need send to someone msg
one: Vec<(PeerId, P)>,
/// when game over, and then engine will call prove method
over: bool,
/// when game started, in the custom mode, it always true after chain_create
started: bool,
}
Supports three methods, corresponding to different return value types. add_all
, add_one
and over
.
Run on Z4 !
Run the defined Handler
to get a complete z4 node, it's simple!
let's create .env
file in current directory.
NETWORK=localhost
GAMES=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
SECRET_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Hardhat default sk!
# START_BLOCK=1 # if not set, will sync from latest block
# RPC_ENDPOINTS=https://xxx.com,https://xxxx.com
# URL_HTTP=http://127.0.0.1:8080
# URL_WEBSOCKET=ws://127.0.0.1:8000
# HTTP_PORT=8080
# WS_PORT=8000
# AUTO_STAKE=true # if true, will stake when starting with URL_HTTP & URL_WEBSOCKET
# ROOM_MARKET=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 # if game and room market not the same contract
# RUST_LOG=info
And now, let's run z4 node!
use z4_engine::{Config, Engine};
struct MyHandler { ... }
#[async_trait::async_trait]
impl Handler for MyHandler { ... }
#[tokio::main]
async fn main() {
let config = Config::from_env().unwrap();
Engine::<MyHandler>::init(config)
.run()
.await
.expect("Down");
}
Great, this is all about using z4 engine to develop a z4 node. For more information, you can refer to the game cases and API documentation.