Modelagem de Dados
A aplicação define estruturas voltadas ao gerenciamento do progresso do jogador e dos elementos da fazenda.
pub struct Agricultor {
pub nivel_habilidade: u64,
pub total_vendido: u64,
}
Os tipos de cultura são representados por um enum que atualmente contém uma única variante:
pub enum TipoCultura {
Tomate: (),
}
Cada cultura plantada carrega o tipo e o momento em que foi semeada:
pub struct Cultura {
pub variedade: TipoCultura,
pub instante_plantio: Option<u64>,
}
O terreno do jogador é modelado como um vetor de tamanho fixo, limitado a dez posições:
pub struct Terreno {
pub canteiros: [Option<Cultura>; 10],
}
Esquema de Persistência
O cnotrato utiliza diversos mapas de armazenamento para manter registros seguros e isolados por identidade:
agricultores– dados do perfil do jogadorsementes_estoque– quantidade de sementes de cada tipocanteiros_plantados– vetor de culturas em crescimentoitens_colhidos– produtos no invantárioposicao_jogador– coordenadas no mundo
Principais Mecânicas Implementadas
Registro de Novo Jogador
A função de inicialização cria um perfil, concede moedas iniciais e prepara o terreno vazio. A verificação impede registros duplicados:
#[storage(read, write)]
fn cadastrar_jogador(ident: Identity) {
require(
storage.agricultores.get(ident).try_read().is_none(),
"perfil já existente",
);
let novo = Agricultor { nivel_habilidade: 1, total_vendido: 0 };
storage.agricultores.insert(ident, novo);
storage.canteiros_plantados.insert(ident, Terreno::novo());
cunhar_moedas(ident, SUB_ID_PADRAO, 1_000_000_000);
log(EventoNovoJogador { endereco: ident });
}
O avanço de nível depende do volume total vendido, calculado como novo_nivel * novo_nivel * 3_000_000. O jogador precisa superar esse valor para habilitar a progressão.
Aquisição e Plantio
Para plantar, é necessário adquirir sementes. O custo de cada tipo é fixo e pago com tokens nativos:
#[storage(read, write), payable]
fn comprar_sementes(cultura: TipoCultura, qtd: u64, destino: Identity) {
let preco_unitario = match cultura {
TipoCultura::Tomate => 750_000,
};
let custo_total = qtd * preco_unitario;
require(msg_amount() >= custo_total, Erro::SaldoInsuficiente);
let saldo_atual = storage.sementes_estoque.get((destino, cultura)).try_read().unwrap_or(0);
storage.sementes_estoque.insert((destino, cultura), saldo_atual + qtd);
}
O plantio consome uma semente e insere a cultura em um índice específico do terreno. O instante de semeadura é registrado com o timestamp corrente:
#[storage(read, write)]
fn semear_no_indice(cultura: TipoCultura, idx: u64, ident: Identity) {
let qtd_atual = storage.sementes_estoque.get((ident, cultura)).try_read().unwrap_or(0);
require(qtd_atual >= 1, Erro::SementesFaltando(1));
storage.sementes_estoque.insert((ident, cultura), qtd_atual - 1);
let mut terreno = storage.canteiros_plantados.get(ident).try_read().unwrap();
let nova_cultura = Cultura {
variedade: cultura,
instante_plantio: Some(timestamp()),
};
terreno.canteiros[idx] = Some(nova_cultura);
storage.canteiros_plantados.insert(ident, terreno);
}
É possível acelerar o crescimento pagando taxas adicionais; o tempo de maturação é reduzido somando‑se 600 segundos ao instante de plantio.
Colheita e Comercialização
A colheita verifica se cada cultura já atingiu a maturidade (considerando um período fixo de 600 segundos) e transfere o item para o inventário:
#[storage(read, write)]
fn colher(indices: Vec<u64>, ident: Identity) {
let agora = timestamp();
let duracao_crescimento = 120 * 5;
let mut terreno = storage.canteiros_plantados.get(ident).try_read().unwrap();
let mut idx = 0;
while idx < indices.len() {
let pos = indices.get(idx).unwrap();
let cultura = terreno.canteiros[pos].as_ref().unwrap();
let plantio = cultura.instante_plantio.unwrap();
let pronto_em = plantio + duracao_crescimento;
require(agora >= pronto_em, "cultura imatura");
terreno.canteiros[pos] = None;
let qtd = storage.itens_colhidos.get((ident, cultura.variedade)).try_read().unwrap_or(0);
storage.itens_colhidos.insert((ident, cultura.variedade), qtd + 1);
idx += 1;
}
storage.canteiros_plantados.insert(ident, terreno);
}
Os produtos colhidos podem ser vendidos por moedas, atualizando o total vendido do jogador:
#[storage(read, write)]
fn vender_item(cultura: TipoCultura, qtd: u64, ident: Identity) {
let em_estoque = storage.itens_colhidos.get((ident, cultura)).try_read().unwrap_or(0);
require(em_estoque >= qtd, Erro::EstoqueInsuficiente);
let valor_venda = match cultura {
TipoCultura::Tomate => 7_500_000 * qtd,
};
let mut perfil = storage.agricultores.get(ident).try_read().unwrap();
perfil.total_vendido += valor_venda;
storage.agricultores.insert(ident, perfil);
storage.itens_colhidos.insert((ident, cultura), em_estoque - qtd);
cunhar_moedas(ident, SUB_ID_PADRAO, valor_venda);
}
Proteções e Boas Práticas
- Toda operação sensível valida entradas com
require, evitando estouros de quantidade ou acessos não autorizados. - Um enum
Erropadroniza mensagens para falta de saldo, sementes e ativos incorretos. - Eventos são emitidos (
log) em ações importantes, como compra de sementes e colheita, facilitando a observação off‑chain. - As funções recebem explicitamente a identidade do jogador, garantindo que ninguém manipule dados alheios.
Integração com o Frontend
O lado cliente comunica‑se com o contrato por meio de chamadas tipadas, utilizando o SDK TypeScript do Fuel. A execução de funções prepara a transação com os parâmetros e o valor em tokens necessário:
async comprarSementes(tipo: TipoCultura, quantidade: number, endereco: string): Promise<void> {
const preco = tipo === TipoCultura.Tomate ? 750000 : 0;
const custo = preco * quantidade;
const tx = this.contrato.functions.comprar_sementes(tipo, quantidade, endereco)
.callParams({ amount: custo, assetId: BaseAssetId });
await tx.waitForResult();
}
Componentes como BuySeeds.tsx e MarketModal.tsx renderizam a interface de mercado e gerenciam o ciclo de jogo.
A estrutura modular do sway-farm facilita a extensão: novos tipos de cultura, sistemas de negociação entre jogadores, eventos climáticos e integração com NFTs são caminhos naturais de evolução. O projeto demonstra a aplicação prática da linguagem Sway em jogos totalmente on‑chain.