准备城堡游戏数据

在学习了 Move 和 Sui 的基础知识后,本章将引导我们完成 Move Castle 游戏。

在深入了解游戏机制核心逻辑的数学之前,我们首先需要为 Move Castle 游戏准备游戏数据。 我们在之前的课程中已经介绍了 GameStoreCastleData,现在,我们只需要在建造城堡时初始化它们。

要确定城堡的初始游戏数据(例如攻击力和防御力),需要依赖于城堡的大小和种族。在 core.move 模块中添加一个 get_initial_attack_defense_power 函数:

/// 通过种族获取初始攻击力和防御力
fun get_initial_attack_defense_power(race: u64): (u64, u64) {
    let (attack, defense);

    if (race == 0) {
        (attack, defense) = (1000, 1000);
    } else if (race == 1) {
        (attack, defense) = (500, 1500);
    } else if (race == 2) {
        (attack, defense) = (1500, 500);
    } else if (race == 3) {
        (attack, defense) = (1200, 800);
    } else if (race == 4) {
        (attack, defense) = (800, 1200);
    } else {
        abort 0
    };

    (attack, defense)
}

代码中的数字源自游戏数据设计。然而,很明显,这段代码的实现充满了“魔法数字”,使审阅者或项目维护者在阅读代码时难以理解其含义。 一个广泛采用的良好实践是将这些数字定义为常量

/// 通过种族获取初始攻击力和防御力
fun get_initial_attack_defense_power(race: u64): (u64, u64) {
    let (attack, defense);

    if (race == CASTLE_RACE_HUMAN) {
        (attack, defense) = (INITIAL_ATTCK_POWER_HUMAN, INITIAL_DEFENSE_POWER_HUMAN);
    } else if (race == CASTLE_RACE_ELF) {
        (attack, defense) = (INITIAL_ATTCK_POWER_ELF, INITIAL_DEFENSE_POWER_ELF);
    } else if (race == CASTLE_RACE_ORCS) {
        (attack, defense) = (INITIAL_ATTCK_POWER_ORCS, INITIAL_DEFENSE_POWER_ORCS);
    } else if (race == CASTLE_RACE_GOBLIN) {
        (attack, defense) = (INITIAL_ATTCK_POWER_GOBLIN, INITIAL_DEFENSE_POWER_GOBLIN);
    } else if (race == CASTLE_RACE_UNDEAD) {
        (attack, defense) = (INITIAL_ATTCK_POWER_UNDEAD, INITIAL_DEFENSE_POWER_UNDEAD);
    } else {
        abort 0
    };

    (attack, defense)
}

/// 城堡种族 - 人类
const CASTLE_RACE_HUMAN : u64 = 0;
/// 城堡种族 - 精灵
const CASTLE_RACE_ELF : u64 = 1;
/// 城堡种族 - 兽人
const CASTLE_RACE_ORCS : u64 = 2;
/// 城堡种族 - 哥布林
const CASTLE_RACE_GOBLIN : u64 = 3;
/// 城堡种族 - 僵尸
const CASTLE_RACE_UNDEAD : u64 = 4;

/// 初始攻击力 - 人类城堡
const INITIAL_ATTCK_POWER_HUMAN : u64 = 1000;
/// 初始攻击力 - 精灵城堡
const INITIAL_ATTCK_POWER_ELF : u64 = 500;
/// 初始攻击力 - 兽人城堡
const INITIAL_ATTCK_POWER_ORCS : u64 = 1500;
/// 初始攻击力 - 哥布林城堡
const INITIAL_ATTCK_POWER_GOBLIN : u64 = 1200;
/// 初始攻击力 - 僵尸城堡
const INITIAL_ATTCK_POWER_UNDEAD : u64 = 800;

/// 初始防御力 - 人类城堡
const INITIAL_DEFENSE_POWER_HUMAN : u64 = 1000;
/// 初始防御力 - 精灵城堡
const INITIAL_DEFENSE_POWER_ELF : u64 = 1500;
/// 初始防御力 - 兽人城堡
const INITIAL_DEFENSE_POWER_ORCS : u64 = 500;
/// 初始防御力 - 哥布林城堡
const INITIAL_DEFENSE_POWER_GOBLIN : u64 = 800;
/// 初始防御力 - 僵尸城堡
const INITIAL_DEFENSE_POWER_UNDEAD : u64 = 1200;

同样,初始经济实力依赖于城堡的大小,在同一个模块中添加 get_initial_economic_power 函数:

// 通过城堡大小获取初始经济实力
fun get_initial_economic_power(size: u64): u64 {
    let power;
    if (size == CASTLE_SIZE_SMALL) {
        power = INITIAL_ECONOMIC_POWER_SMALL_CASTLE;
    } else if (size == CASTLE_SIZE_MIDDLE) {
        power = INITIAL_ECONOMIC_POWER_MIDDLE_CASTLE;
    } else if (size == CASTLE_SIZE_BIG) {
        power = INITIAL_ECONOMIC_POWER_BIG_CASTLE;
    } else {
        abort 0
    };
    power
}

/// 城堡大小 - 小型
const CASTLE_SIZE_SMALL : u64 = 1;
/// 城堡大小 - 中型
const CASTLE_SIZE_MIDDLE : u64 = 2;
/// 城堡大小 - 大型
const CASTLE_SIZE_BIG : u64 = 3;

/// 初始经济实力 - 小型城堡
const INITIAL_ECONOMIC_POWER_SMALL_CASTLE : u64 = 100;
/// 初始经济实力 - 中型城堡
const INITIAL_ECONOMIC_POWER_MIDDLE_CASTLE : u64 = 150;
/// 初始经济实力 - 大型城堡
const INITIAL_ECONOMIC_POWER_BIG_CASTLE : u64 = 250;

别忘了初始化城堡的总攻击/防御力,它由基础力量和士兵的力量组成。士兵的攻击力和防御力取决于他们的种族,要获得单个士兵的力量,在 core.move 中添加这个函数:

/// 城堡单个士兵的攻击力和防御力
public(package) fun get_castle_soldier_attack_defense_power(race: u64): (u64, u64) {
    let soldier_attack_power;
    let soldier_defense_power;
    if (race == CASTLE_RACE_HUMAN) {
        soldier_attack_power = SOLDIER_ATTACK_POWER_HUMAN;
        soldier_defense_power = SOLDIER_DEFENSE_POWER_HUMAN;
    } else if (race == CASTLE_RACE_ELF) {
        soldier_attack_power = SOLDIER_ATTACK_POWER_ELF;
        soldier_defense_power = SOLDIER_DEFENSE_POWER_ELF;
    } else if (race == CASTLE_RACE_ORCS) {
        soldier_attack_power = SOLDIER_ATTACK_POWER_ORCS;
        soldier_defense_power = SOLDIER_DEFENSE_POWER_ORCS;
    } else if (race == CASTLE_RACE_GOBLIN) {
        soldier_attack_power = SOLDIER_ATTACK_POWER_GOBLIN;
        soldier_defense_power = SOLDIER_DEFENSE_POWER_GOBLIN;
    } else if (race == CASTLE_RACE_UNDEAD) {
        soldier_attack_power = SOLDIER_ATTACK_POWER_UNDEAD;
        soldier_defense_power = SOLDIER_DEFENSE_POWER_UNDEAD;
    } else {
        abort 0
    };

    (soldier_attack_power, soldier_defense_power)
}

/// 通过种族和士兵数量获取初始士兵的攻击力和防御力
fun get_initial_soldiers_attack_defense_power(race: u64, soldiers: u64): (u64, u64) {
    let (attack, defense) = get_castle_soldier_attack_defense_power(race);
    (attack * soldiers, defense * soldiers)
}

/// 士兵攻击力 - 人类
const SOLDIER_ATTACK_POWER_HUMAN : u64 = 100;
/// 士兵防御力 - 人类
const SOLDIER_DEFENSE_POWER_HUMAN : u64 = 100;
/// 士兵攻击力 - 精灵
const SOLDIER_ATTACK_POWER_ELF : u64 = 50;
/// 士兵防御力 - 精灵
const SOLDIER_DEFENSE_POWER_ELF : u64 = 150;
/// 士兵攻击力 - 兽人
const SOLDIER_ATTACK_POWER_ORCS : u64 = 150;
/// 士兵防御力 - 兽人
const SOLDIER_DEFENSE_POWER_ORCS : u64 = 50;
/// 士兵攻击力 - 哥布林
const SOLDIER_ATTACK_POWER_GOBLIN : u64 = 120;
/// 士兵防御力 - 哥布林
const SOLDIER_DEFENSE_POWER_GOBLIN : u64 = 80;
/// 士兵攻击力 - 僵尸
const SOLDIER_ATTACK_POWER_UNDEAD : u64 = 120;
/// 士兵防御力 - 僵尸
const SOLDIER_DEFENSE_POWER_UNDEAD : u64 = 80;

然后我们在 core.move 模块中添加一个 init_castle_data 函数,并初始化 CastleData 对象:

/// 初始化城堡数据
public(package) fun init_castle_data(id: ID,
                            size: u64,
                            race: u64,
                            current_timestamp: u64,
                            game_store: &mut GameStore) {
    // 1. 获取初始力量并初始化城堡数据
    let (attack_power, defense_power) = get_initial_attack_defense_power(race);
    let (soldiers_attack_power, soldiers_defense_power) = get_initial_soldiers_attack_defense_power(race, INITIAL_SOLDIERS);
    let castle_data = CastleData {
        id: id,
        size: size,
        race: race,
        level: 1,
        experience_pool: 0,
        economy: Economy {
            treasury: 0,
            base_power: get_initial_economic_power(size),
            settle_time: current_timestamp,
            soldier_buff: EconomicBuff {
                debuff: false,
                power: SOLDIER_ECONOMIC_POWER * INITIAL_SOLDIERS,
                start: current_timestamp,
                end: 0
            },
            battle_buff: vector::empty<EconomicBuff>()
        },
        millitary: Millitary {
            attack_power: attack_power,
            defense_power: defense_power,
            total_attack_power: attack_power + soldiers_attack_power,
            total_defense_power: defense_power + soldiers_defense_power,
            soldiers: INITIAL_SOLDIERS,
            battle_cooldown: current_timestamp
        }
    };
}

/// 初始士兵数量
const INITIAL_SOLDIERS : u64 = 10;
/// 士兵经济力量
const SOLDIER_ECONOMIC_POWER : u64 = 1;

还记得我们之前学过的动态字段吗?让我们通过使用 动态字段 将城堡数据存储在游戏存储对象下面来实践它:

use sui::dynamic_field;

// 2. 存储城堡数据
dynamic_field::add(&mut game_store.id, id, castle_data);

还需要更新其他一些游戏数据:

// 3. 更新城堡 ID 和城堡数量
vector::push_back(&mut game_store.castle_ids, id);
if (size == CASTLE_SIZE_SMALL) {
    game_store.small_castle_count = game_store.small_castle_count + 1;
} else if (size == CASTLE_SIZE_MIDDLE) {
    game_store.middle_castle_count = game_store.middle_castle_count + 1;
} else if (size == CASTLE_SIZE_BIG) {
    game_store.big_castle_count = game_store.big_castle_count + 1;
} else {
    abort 0
};

因此,整个 init_castle_data 函数如下:

/// 初始化城堡数据
public(package) fun init_castle_data(id: ID,
                            size: u64,
                            race: u64,
                            current_timestamp: u64,
                            game_store: &mut GameStore) {
    // 1. 获取初始力量并初始化城堡数据
    let (attack_power, defense_power) = get_initial_attack_defense_power(race);
    let (soldiers_attack_power, soldiers_defense_power) = get_initial_soldiers_attack_defense_power(race, INITIAL_SOLDIERS);
    let castle_data = CastleData {
        id: id,
        size: size,
        race: race,
        level: 1,
        experience_pool: 0,
        economy: Economy {
            treasury: 0,
            base_power: get_initial_economic_power(size),
            settle_time: current_timestamp,
            soldier_buff: EconomicBuff {
                debuff: false,
                power: SOLDIER_ECONOMIC_POWER * INITIAL_SOLDIERS,
                start: current_timestamp,
                end: 0
            },
            battle_buff: vector::empty()
        },
        millitary: Millitary {
            attack_power: attack_power,
            defense_power: defense_power,
            total_attack_power: attack_power + soldiers_attack_power,
            total_defense_power: defense_power + soldiers_defense_power,
            soldiers: INITIAL_SOLDIERS,
            battle_cooldown: current_timestamp
        }
    };

    // 2. 存储城堡数据
    dynamic_field::add(&mut game_store.id, id, castle_data);

    // 3. 更新城堡 ID 和城堡数量
    vector::push_back(&mut game_store.castle_ids, id);
    if (size == CASTLE_SIZE_SMALL) {
        game_store.small_castle_count = game_store.small_castle_count + 1;
    } else if (size == CASTLE_SIZE_MIDDLE) {
        game_store.middle_castle_count = game_store.middle_castle_count + 1;
    } else if (size == CASTLE_SIZE_BIG) {
        game_store.big_castle_count = game_store.big_castle_count + 1;
    } else {
        abort 0
    };
}

在建造城堡时初始化城堡数据,在 castle.move 中的 build_castle 函数中添加以下逻辑:

use sui::clock::{Self, Clock};
use move_castle::core::{Self, GameStore};

/// 建造城堡
entry fun build_castle(size: u64, name_bytes: vector<u8>, desc_bytes: vector<u8>, clock: &Clock, game_store: &mut GameStore, ctx: &mut TxContext) {
    ...
    // 新的城堡对象
    let castle = Castle {...

    // 新的城堡游戏数据
    let id = object::uid_to_inner(&castle.id);
    let race = get_castle_race(serial_number);
    core::init_castle_data(
        id, 
        size,
        race,
        clock::timestamp_ms(clock),
        game_store
    );

    // 将城堡对象转移给所有者
    ...
}

/// 获取城堡种族
public fun get_castle_race(serial_number: u64): u64 {
    let mut race_number = serial_number % 10;
    if (race_number >= 5) {
        race_number = race_number - 5;
    };
    race_number
}