前言
官方又发布新版本了,目前写的合约也部署不上了,新版本的语法变了确实蛮多的,目前仅按照官网的教程把流程过一遍了
很幸运,目前的官方的游戏第一次和第二次已经被我玩了,这里也把第二次的游戏流程分享给大家,对于接下来编写代码心里有点底,因为本课主要是智能合约的编写。
Elemental Battles是一款单人纸牌游戏,其中包括元素之间的战斗。对手将通过在EOSIO上运行的智能合约来代表。
学习目标
在本课中,我们将准备我们的游戏。我们还将在智能合约中构建startgame和playcard动作,并从前端触发。在本课程结束时,您将能够开始玩元素战斗并迈出第一步!
合约编写
这里我暂时无法部署上去,只能先把代码放在这里了。看了上面的游戏玩法,各位同学结合注释应该看得明白了。
玩家和ai进行卡片游戏对战,都从各自的待选17张牌中选出四张,每张牌有不同的攻击力。根据攻击力进行判断一次对决的胜负,各有5条命,输一次减少一条命,谁先到达0谁就输了。游戏简单,主要是了解一下智能合约的开发流程
这里并没有成功布置到链上,以后补上了
第四课源码
cardgame.hpp
#include <eosiolib/eosio.hpp>
using namespace std;
class cardgame : public eosio::contract {
// 继承contract
public:
//构造方法,并实例化了multi_index表users
cardgame( account_name self):contract(self),
_users(self,self),
_seed(self,self){};
// table和action 必须加上@abi 的注解,这样才能生存对应的abi,
// 在eosio.cdt中,使用 [[eosio::action]] 更加简洁
[[eosio::action]]
void login(account_name user);
// 开始游戏,分发手里牌
[[eosio::action]]
void startgame(account_name user);
//从手上牌选择卡片进行对战
[[eosio::action]]
void playcard(account_name user,uint8_t player_card_index);
private:
enum game_status:int8_t
{
ONGOING = 0, // 游戏正在进行中
PLAYER_WON = 1, // 游戏已经结束,玩家获得胜利
PLAYER_LOST = -1 // 游戏结束, 完结失败
};
enum card_type : uint8_t // 卡片的类型 总共五种属性类型,总共17张卡牌
{
EMPTY = 0, // 不存在的卡片
FIRE = 1, // 火属性, 克木 攻击力为 1 和 2 的各两张 攻击力为3的一张 总共5张
WOOD = 2, // 木属性, 克水 攻击力为 1 和 2 的各两张 攻击力为3的一张 总共5张
WATER = 3, // 水属性 克火 攻击力为 1 和 2 的各两张 攻击力为3的一张 总共5张
NEUTRAL = 4, // 中立属性 攻击力为3 总共1张
VOID = 5 // 平局属性 共计力为0 总共1张
};
struct card
{
uint8_t type; // 卡片类型
uint8_t attack_point; // 卡片的攻击力
};
typedef uint8_t card_id;
const map<card_id,card> card_dict = {
{0 , {EMPTY , 0}},
{1 , {EMPTY , 1}},
{2 , {EMPTY , 1}},
{3 , {EMPTY , 2}},
{4 , {EMPTY , 2}},
{5 , {EMPTY , 3}},
{6 , {EMPTY , 1}},
{7 , {EMPTY , 1}},
{8 , {EMPTY , 2}},
{9 , {EMPTY , 2}},
{10 , {EMPTY , 3}},
{11 , {EMPTY , 1}},
{12 , {EMPTY , 1}},
{13 , {EMPTY , 2}},
{14 , {EMPTY , 2}},
{15 , {EMPTY , 3}},
{16 , {EMPTY , 3}},
{17 , {EMPTY , 0}},
};
struct game
{
int8_t status = ONGOING; // 只要登录 默认游戏正在进行
int8_t life_player = 5; // 游戏玩家 5条生命
int8_t ai_player = 5; // 游戏玩家 5条生命
vector<card_id> deck_player = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}; // 玩家待选的卡牌id
vector<card_id> deck_ai = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}; // ai待选的卡牌id
vector<card_id> hand_player = {0,0,0,0}; // 玩家的手里牌
vector<card_id> hand_ai = {0,0,0,0}; // ai的手里牌
card_id selected_card_player = 0; // 从待选牌中选中的牌
card_id selected_card_ai = 0; // 从待选牌中选中的牌
};
[[eosio::table]]
struct userinfo
{
account_name name; //玩家的名字 account_name 是uint64_t的一个别名
uint16_t win_count = 0;
uint16_t lost_count = 0;
game game_data; // game
auto primary_key() const {return name;}
//多索引表查找名为primary_key()的getter函数。这必须使用结构中的第一个字段,编译器将使用它来添加主键。让我们定义这个功能。
};
[[eosio::table]]
struct seed // 这个是用来生成随机数的,全局储存,只要有人玩就会改变,没办法,eos里只能用table来持久化数据在链上
{
uint64_t key = 1;
uint32_t value = 1;
auto primary_key(){return key;};
}
typedef eosio::multi_index<N(userinfo),userinfo> user_index;
//多索引表定义,它有两个参数: 表名
// 结构定义了我们打算在多索引表中存储的数据。
// 新类型名称作为我们的多索引表定义的别名
//
// 多索引表中的数据由四条信息标识: code(account_name)
// scop
// table name
// primary key
typedef multi_index<N(seed),seed> seed_index;
user_index _users; //声明表的实例
seed_index _seed;
int random(const int range);
void draw_one_card(vector<uint8_t>& deck,vector<uint8_t>& hand);
};
cardgame.cpp
#include "cardgame.hpp"
void cardgame::login(account_name user)
{
require_auth(user);
//确保这个用户已经被授权
// 这里我理解了很久,把我的想法记录在这里
// 这里是调用此合约的account name,即提供私钥的account,这里传入的这个account必须拥有它的active权限,
// 当然,这里还可以要求有其他的权限,这样就可以给特殊的账号特殊的权限
auto user_iterator = _users.find(user); // 查找
if (user_iterator == _users.end()) {
user_iterator = _users.emplace(user, [&](auto& new_user){
new_user.name = user;
});
}
// 这里使用multi_index的find 和 emplace(插入)两种方法,
// 这些方法的形式都是一样的,记住就好,可以去官网查看详细的方法说明
}
void cardgame::startgame(account_name name) {
require_auth(name);
auto& info = _users.get(name, "user not exist");
_users.modify(info,name,[&](auto& _to_modify_user){
game game_data;
for(uint8_t i = 0; i < 4; i++){
draw_one_card(game_data.deck_player,game_data.hand_player);
draw_one_card(game_data.deck_ai,game_data.hand_ai);
}
_to_modify_user.game_data = game_data;
});
}
void cardgame::playcard(account_name user,uint8_t player_card_index){
require_auth(user);
eosio.assert(player_card_index < 4,"invalid hand index"); // 手上牌最多四张
// 通过user找到数据表中数据
auto& player = _users.get(user,"User not exist");
eosio_assert(player.game_data.status == ONGONING,"game have ended"); // 确保本轮游戏没有结束
eosio_assert(player.game_data.selected_card_player == 0,"the player have selected car in this turn"); // 确保手上没有选牌
// 修改数据表
_users.modify(player,user,[&](auto& _to_modify_user){
game& game_data = _to_modify_user.game_data;
// 设定选中的卡片id
game_data.selected_card_player = game_data.hand_player[player_card_index];
// 将手中卡片对应位置置位empty
game_data.hand_player[player_card_index] = 0;
});
}
EOSIO_ABI(cardgame,(login)(playcard))
gameplay.cpp
#include "cardgame.hpp"
int cardgame::random(const int range) {
auto seed_itr = _seed.begin();
if (seed_itr == _seed.end())
{ // 如果没有随机数据,就用默认值初始化
seed_itr = _seed.emplace(_self,[&](auto& seed){})
};
int prime = 65535;
// 新值得范围 0 ~ 65535
auto new_seed_value = (seed_itr.value + now()) % prime;
_seed.modify(seed_itr,_self,[&](auto& s){
s.value = new_seed_value;
});
// 随机范围 0 ~ range
int random_res = new_seed_value % range;
return random_res;
}
// 根据待选卡片和手上已有的卡片,随机进行选择新的卡片
void cardgame::draw_one_card(vector<uint8_t> &deck,vector<uint8_t> &hand){
// 0 ~ 16
int deck_card_index = random(deck.size());
int first_emprty_slot = -1;
for (int i = 0; i < hand.size; ++i)
{
auto id = hand[i];
if (card_dict.at(id).type == EMPTY){
first_emprty_slot = i;
break;
}
}
eosio_assert(first_emprty_slot !=- 1, "No empty slot in the players hand");
// 如果手上的牌 有空位,进行随机赋值
hand[first_emprty_slot] = deck[deck_card_index];
// 待选牌中移除掉已经被赋值给手上的牌
deck.erase(deck.began() + deck_card_index);
}
关于我
区块链技术痴迷的程序猿一枚,如果你喜欢我的文章,可以加上微信共同学习,共同进步。