本课程通过两个 Tamagotchi 合约梳理 Tamagotchi 战斗逻辑与实施过程。
本课的重点是促进两个 Tamagotchi 之间战斗的合约。
战斗合约可以处于四种状态之一:Registration、Moves、Waiting 和 GameIsOver。
Battle 结构包含有关玩家的信息、游戏的当前状态、当前回合、Tamagotchi 商店 ID、获胜者以及到目前为止所走的步数。
要参与游戏,用户必须允许他们的合约接收与游戏相关的消息,如 TmgAction::TmgInfo 和 StoreAction::GetAttributes。
注册函数从商店获取 Tamagotchi 主人的信息及其属性,随机生成 Tamagotchi 的力量和能量,并注册 Tamagotchi。
get_owner 和 get_attributes 函数分别从商店中检索有关 Tamagotchi 所有者和属性的信息,而 get_turn 和 generate_power 函数分别生成伪随机数以确定谁开始游戏和 Tamagotchi 的功率。
在课程结束时,你会学习到:
理解促成两个 Tamagotchi 之间战斗的合约。
了解 Battle 结构体中包含的信息,例如玩家信息、游戏状态、获胜者和到目前为止所走的步数。
领悟战斗合约状态并理解它们的作用
了解用于从商店检索信息和生成伪随机数的函数,例如 get_owner、get_attributes、get_turn 和 generate_power。
完成 Tamagotchi 对战的简单实现
战斗合约可以有 3 种状态:
Registration
:战斗合约等待 Tamagotchi 主人注册;
Move
:Tamagotchi 所有者轮流移动;
Waiting
:在 Tamagotchi 主人采取行动后,战斗合约让他们有时间装备 Tamagotchi。
GameIsOver
:战斗结束,需要发送消息StartNewGame
。
enum BattleState {
Registration,
Moves,
Waiting,
GameIsOver,
}
impl Default for BattleState {
fn default() -> Self {
BattleState::Registration
}
}
战斗程序状态:
#[derive(Default)]
pub struct Battle {
players: Vec<Player>,
state: BattleState,
current_turn: u8,
tmg_store_id: ActorId,
winner: ActorId,
steps: u8,
}
玩家结构如下:
#[derive(Default)]
pub struct Player {
owner: ActorId,
tmg_id: TamagotchiId,
energy: u16,
power: u16,
attributes: BTreeSet<AttributeId>,
}
要参与,用户必须允许他们的合约接收与游戏相关的消息,如下所示:
TmgAction::TmgInfo
它将响应有关 Tamagotchi 所有者的信息:
TmgEvent::Owner(ActorId)
我们还将添加消息
StoreAction::GetAttributes
到允许我们获得 Tamagotchi 属性的商店合约
StoreEvent::Attributes {
attributes: BTreeSet<AttributeId>
}
register
方法:
它允许注册两个 Tamagotchi 进行战斗。
在你注册 Tamagotchi 之前,战斗合约必须从商店接收 Tamagotchi 的所有者及其属性;
收到详情后,战斗合约随机生成 Tamagotchi 的力量和能量;
如果一只 Tamagotchi 被注册,它会保持在注册状态;
如果注册了两个 Tamagotchi,战斗合约将随机确定谁先开始游戏并进入 Moves
状态。
async fn register(&mut self, tmg_id: &TamagotchiId) {
assert_eq!(
self.state,
BattleState::Registration,
"The game has already started"
);
let owner = get_owner(tmg_id).await;
let attributes = get_attributes(&self.tmg_store_id, tmg_id).await;
let power = generate_power();
let power = MAX_power - power;
let player = Player {
owner,
tmg_id: *tmg_id,
energy,
power,
attributes,
};
self.players.push(player);
if self.players.len() == 2 {
self.current_turn = get_turn();
self.state = BattleState::Moves;
}
msg::reply(BattleEvent::Registered { tmg_id: *tmg_id }, 0)
.expect("Error during a reply `BattleEvent::Registered");
}
get_owner
函数从 Tamagotchi 合约中检索 Tamagotchi 的所有者。
pub async fn get_owner(tmg_id: &ActorId) -> ActorId {
let reply: TmgEvent = msg::send_for_reply_as(*tmg_id, TmgAction::Owner, 0)
.expect("Error in sending a message `TmgAction::Owner")
.await
.expect("Unable to decode TmgEvent");
if let TmgEvent::Owner(owner) = reply {
owner
} else {
panic!("Wrong received message");
}
}
同样,get_attributes
函数从商店中检索 Tamagotchi 的属性。
async fn get_attributes(tmg_store_id: &ActorId, tmg_id: &TamagotchiId) -> BTreeSet<AttributeId> {
let reply: StoreEvent = msg::send_for_reply_as(
*tmg_store_id,
StoreAction::GetAttributes {
Tamagotchi_id: *tmg_id,
},
0,
)
.expect("Error in sending a message `StoreAction::GetAttributes")
.await
.expect("Unable to decode `StoreEvent`");
if let StoreEvent::Attributes { attributes } = reply {
attributes
} else {
panic!("Wrong received message");
}
}
为了确定哪个玩家开始游戏,我们使用 get_turn
函数,它使用伪随机算法选择开始玩家:
pub fn get_turn() -> u8 {
let random_input: [u8; 32] = array::from_fn(|i| i as u8 + 1);
let (random, _) = exec::random(random_input).expect("Error in getting random number");
random[0] % 2
}
genetate_power
函数使用伪随机算法生成 Tamagotchi 的力量值:
pub fn genetate_power() -> u16 {
let random_input: [u8; 32] = array::from_fn(|i| i as u8 + 1);
let (random, _) = exec::random(random_input).expect("Error in getting random number");
let bytes: [u8; 2] = [random[0], random[1]];
let random_power: u16 = u16::from_be_bytes(bytes) % MAX_POWER;
if random_power < MIN_POWER {
return MAX_POWER / 2;
}
random_power
}
还有两个常量 MAX_POWER
和 MIN_POWER
,它们定义了 Tamagotchi 力量的上限和下限:
const MAX_POWER: u16 = 10_000;
const MIN_POWER: u16 = 3_000;
接下来,作为例子,我们将定义一个非常简单的游戏机制:
Tamagotchi 所有者只需向战斗合约发送一条消息 BattleAction::Move
即可采取行动。在这一步中,Tamagotchi 击败了对手的 Tamagotchi。对手的能量会随着攻击的力量而减少。
现在,Tamagotchi 商店里只有一个属性可以在游戏中使用——一把剑。如果攻击的 Tamagotchi 有剑,其攻击力乘以 SWORD_POWER:SWORD_POWER * power
,否则 Tamagotchi 的攻击力就是他们的力量。
以后可以通过在商店中添加战斗属性来扩展逻辑,还可以添加 Tamagotchi 在场地上的移动。
fn make_move(&mut self) {
assert_eq!(
self.state,
BattleState::Moves,
"The game is not in `Moves` state"
);
let turn = self.current_turn as usize;
let next_turn = (( turn + 1 ) % 2)as usize;
let player = self.players[turn].clone();
assert_eq!(player.owner,
msg::source(),
"You are not in the game or it is not your turn"
);
let mut opponent = self.players[next_turn].clone();
let sword_power = if player.attributes.contains(&SWORD_ID) {
SWORD_POWER
} else {
1
};
opponent.energy = opponent.energy.saturating_sub(sword_power * player.power);
self.players[next_turn] = opponent.clone();
// check if opponent lost
if opponent.energy == 0 {
self.players = Vec::new();
self.state = BattleState::GameIsOver;
self.winner = player.tmg_id;
msg::reply(BattleEvent::GameIsOver, 0)
.expect("Error in sending a reply `BattleEvent::GameIsOver`");
return;
}
if self.steps <= MAX_STEPS_FOR_ROUND {
self.steps +=å 1;
self.current_turn = next_turn as u8;
msg::reply(BattleEvent::MoveMade, 0)
.expect("Error in sending a reply `BattleEvent::MoveMade`");
} else {
self.state = BattleState::Waiting;
self.steps = 0;
msg::send_with_gas_delayed(
exec::program_id(),
BattleAction::UpdateInfo,
GAS_AMOUNT,
0,
TIME_FOR_UPDATE,
)
.expect("Error in sending a delayed message `BattleAction::UpdateInfo`");
msg::reply(BattleEvent::GoToWaitingState, 0)
.expect("Error in sending a reply `BattleEvent::MoveMade`");
}
}
UpdateInfo
操作只是更新 Tamagotchi 状态的变化并开始下一轮:
async fn update_info(&mut self) {
assert_eq!(
msg::source(),
exec::program_id(),
"Only contract itself can call that action"
);
assert_eq!(
self.state,
BattleState::Waiting,
"The contract must be in `Waiting` state"
);
for i in 0..2 {
let player = &mut self.players[i];
let attributes = get_attributes(&self.tmg_store_id, &player.tmg_id).await;
player.attributes = attributes;
}
self.state = BattleState::Moves;
self.current_turn = get_turn();
msg::reply(BattleEvent::InfoUpdated, 0)
.expect("Error during a reply `BattleEvent::InfoUpdated");
}
至此,Tamagotchi 对战的简单实现就完成了。
你可以尝试与你的 Tamagotchi 一起战斗,或者创建一些电子 Tamagotchi(使用上一课)并让它们战斗(链接到网络应用程序)。
现在是你的课程作业时间。
为商店合约增加更多的战斗属性;
确定这些属性的力量。你还可以添加升级各种属性的机会(这也需要扩展商店合约的逻辑);
允许玩家选择他将在回合中战斗或防御的武器;
也可以为 Tamagotchi 添加向左或向右移动的能力;
发挥创意并考虑其他可能的方法来使游戏更有趣。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!