
[ad_1]
eué um novo dia e um ótimo momento para aprender algo novo; Estou aqui para ajudá-lo a alcançá-lo :). Este artigo ajudará você a criar um jogo da velha P2P (com funcionalidade de jogos de azar) usando Rust (com !ink).
Após essa longa leitura (também prática), você poderá interagir com tokens PSP22 padrão em cadeias Substrate como Aleph Zero (esse é o nosso foco principal de qualquer maneira).
vou demonstrar psp22::balanceOf(), psp22::ajuda(), psp22::transfer() e psp22::transferFrom() métodos PSP22 neste artigo.
Curiosidade: estou transpilando Solidity para Rust !ink e vice-versa, JSYK 😎
Estima-se que duas em cada cinco pessoas conheçam o jogo da velha, embora possa ser referido de maneiras diferentes dependendo da opinião, orientação, terreno ou escolha.
Observe que o código-fonte apresentado neste artigo não foi testado de forma alguma e está sujeito a vulnerabilidades; é puramente para fins educacionais e nada mais.
Se você nunca ouviu falar em jogo da velha, é um jogo de tabuleiro com uma grade quadrada de 3 por 3 células (pode ser mais dependendo da preferência do jogador, como 6 por 6). , 10 em 10, etc.) que é jogado com dois símbolos para cada jogador, geralmente um “X” e um “O”.
Os jogadores fazem movimentos inserindo (desenhando) seu personagem em um quadrado vazio para criar um padrão horizontal, diagonal ou vertical, evitando que os oponentes façam o mesmo e superando-os para alcançar uma linha completa sem ser interrompido. Em cada rodada, se não houver vencedor (ou seja, todas as vagas são preenchidas sem que o jogador insira um padrão válido), o jogo é marcado como empate e uma nova rodada começa.
A imagem abaixo mostra que o jogador com o símbolo “X” é o vencedor em todas as três rodadas (ignore o fato de que “O” não jogou em todas as rodadas; o outro jogador é um tolo).
Neste exemplo, ambos os jogadores apostam uma certa quantia para jogar. O vencedor leva todas as fichas e o jogo acaba, mas se houver empate, as fichas são devolvidas para os dois lados e o jogo acaba. O tabuleiro também é reiniciado após cada rodada de jogo.
Agora que você sabe como o jogo funciona, vamos mergulhar na implementação real com o Rust 😎. legal haha :)
Por favor, siga este link para começar a instalar o “cargo-contract” para desenvolver contratos inteligentes com Rust.
Antes de começar, a estrutura de pastas do projeto precisa ser definida e criaremos dois arquivos que serão usados para criar o contrato.
“cargo.toml” é mais parecido com o conhecido “package.json” do NPM (Node.js) e nos ajuda a definir propriedades, dependências e informações relacionadas ao projeto, enquanto usaremos “lib. rs” como nosso arquivo de entrada (é aqui que todo o levantamento de peso será feito).
Estes são os dois arquivos que serão utilizados neste projeto.
Use o conteúdo deste arquivo para compilar seu arquivo “cargo.toml”.
Basicamente, você precisará alterar o nome e o endereço de e-mail na chave “autores” no arquivo na linha 4 para combinar com o seu.
O arquivo ficará assim:
[package]
name = "tic_tac_toe"
version = "0.8.0"
authors = ["YOUR_NAME <someone@example.com>"]
edition = "2021"
overflow-checks = false[dependencies]
# Import of all ink! crates
ink_primitives = { version = "~3.3.0", default-features = false }
ink_metadata = { version = "~3.3.0", default-features = false, features = ["derive"], optional = true }
ink_env = { version = "~3.3.0", default-features = false }
ink_storage = { version = "~3.3.0", default-features = false }
ink_lang = { version = "~3.3.0", default-features = false }
ink_prelude = { version = "~3.3.0", default-features = false }
ink_engine = { version = "~3.3.0", default-features = false, optional = true }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"], optional = true }
openbrush = { version = "~2.2.0", default-features = false, features = ["psp22"] }
[lib]
overflow-checks = false
name = "tic_tac_toe"
path = "lib.rs"
crate-type = [
# Used for normal contract Wasm blobs.
"cdylib"
]
[profile.release]
overflow-checks = false
[features]
default = ["std"]
std = [
"ink_primitives/std",
"ink_metadata",
"ink_metadata/std",
"ink_env/std",
"ink_storage/std",
"ink_lang/std",
"scale/std",
"scale-info",
"scale-info/std",
# Brush dependency
"openbrush/std",
]
Primeiramente, precisamos criar um módulo e importar todas as dependências necessárias que farão o smart contract funcionar, assim como o código de inicialização do construtor e a estrutura das chaves de armazenamento que serão utilizadas ao longo do código fonte.
#![cfg_attr(not(feature = "std"), no_std)]use ink_lang as ink;
#[cfg(not(feature = "ink-as-dependency"))]
#[ink::contract]
pub mod tic_tac_toe {
use ink_prelude::vec;
use ink_prelude::vec::Vec;
use ink_storage::traits::SpreadAllocate;
use openbrush::contracts::traits::psp22::PSP22Ref;
use ink_env::{CallFlags};
#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct TicTacToe {
board: Vec<u64>, //0 to 8 cells
turn: AccountId,
symbols: ink_storage::Mapping<AccountId, u64>,
player_one: AccountId,
player_two: AccountId,
staking_token: AccountId,
stake_amount: Balance,
stakes: ink_storage::Mapping<AccountId, Balance>,
last_winner: AccountId,
}
}
As chaves definidas na estrutura de armazenamento “TicTacToe” serão discutidas posteriormente neste artigo; não entrar em pânico :).
A próxima coisa que queremos fazer é definir um construtor na implementação do módulo “tic_tac_toe” logo após a estrutura “TicTacToe” que será chamada quando nossos contratos inteligentes forem implantados para inicializar alguns dos valores que queremos registrar. tem um padrão.
Queremos determinar os endereços dos jogadores, o token PSP22 a ser usado no jogo e seus símbolos e o valor da aposta para esse jogo.
Eles podem ser passados como argumentos para o construtor da seguinte maneira:
impl TicTacToe {
/// Creates a new instance of this contract.
#[ink(constructor)]
pub fn new(player_one:AccountId, player_two:AccountId, player_one_symbol:u64, player_two_symbol:u64, staking_token: AccountId, stake_amount: Balance) -> Self {
//Do something here...
}
}
Dentro do método “new()”, precisamos armazenar os valores e também fazer algumas verificações de rotina para garantir que não haja anomalias no comportamento do contrato durante o jogo.
let me = ink_lang::utils::initialize_contract(|contract: &mut Self| {let board = vec![0; 9]; //empty array
contract.board = board; //set board to empty state
contract.staking_token = staking_token; //set staking token
contract.stake_amount = stake_amount; //set stake amount
assert!(player_one != player_two); //addresses must not be the same
assert!(player_one_symbol != player_two_symbol); //symbols must be distinct
assert!((player_one_symbol == 1 || player_one_symbol == 2) && (player_two_symbol == 1 || player_two_symbol == 2)); //symbols must be either 1 or 2
contract.player_one = player_one; //set player one address
contract.player_two = player_two; //set player two address
contract.symbols.insert(player_one, &player_one_symbol); //set player one symbol
contract.symbols.insert(player_two, &player_two_symbol); //set player two symbol
contract.turn = player_one; //initialize turn to player one
});
me
Neste exemplo, usaremos 0 como uma célula vazia, 1 como um X e 2 como um O. Também usaremos os números de 0 a 8 para denotar as células do quadro, começando do canto superior esquerdo para o canto inferior direito , o que significa que a célula superior esquerda é 0 , a inferior direita é 8 e a célula central é 5.
Analisando o código acima de cima para baixo, vemos que um array vazio é criado, e iteramos por ele para inserir um 0 em cada índice de 0 a 8, significando um tabuleiro vazio para começar.
Também definimos o token de aposta de seu argumento, bem como o valor da aposta.
Queremos garantir que o primeiro jogador não use o mesmo endereço que o segundo jogador e que não seja atribuído a ambos o mesmo símbolo (restrito a 1 e 2). Definimos os endereços do primeiro e do segundo jogadores usando os argumentos fornecidos.
Depois também inserimos no mapeamento os personagens selecionados por ambos os jogadores, com seus endereços como chaves e os personagens selecionados como valores em u64 modelo.
Então, finalmente colocamos o primeiro jogador no turn.
Viva! Terminamos nosso construtor!
Embora queiramos fazer um jogo intrigante, gostaríamos de saber o que está acontecendo no contrato inteligente no que diz respeito ao armazenamento e à dinâmica, então criaremos alguns métodos para nos fornecer os valores de armazenamento desejados.
#[ink(message)]
pub fn get_stake_amount(&self) -> Balance {
self.stake_amount //amount to be staked in game
}#[ink(message)]
pub fn get_last_winner(&self) -> AccountId {
self.last_winner //address of most recent winner
}
#[ink(message)]
pub fn get_current_turn(&self) -> AccountId {
self.turn //who is meant to play?
}
#[ink(message)]
pub fn get_staking_token(&self) -> AccountId {
self.staking_token //get address of staking token smart contract
}
#[ink(message)]
pub fn get_player_two_stake(&self) -> Balance {
self.stakes.get(self.player_two).unwrap_or(0) //get total amount of tokens staked by player two
}
#[ink(message)]
pub fn get_player_one_stake(&self) -> Balance {
self.stakes.get(self.player_one).unwrap_or(0) //get total amount of tokens staked by player one
}
#[ink(message)]
pub fn get_player_two_symbol(&self) -> u64 {
self.symbols.get(self.player_two).unwrap_or(0) //get player two symbol
}
#[ink(message)]
pub fn get_player_one(&self) -> AccountId {
self.player_one //get player one address
}
#[ink(message)]
pub fn get_player_two(&self) -> AccountId {
self.player_two //get player two address
}
#[ink(message)]
pub fn get_player_one_symbol(&self) -> u64 {
self.symbols.get(self.player_one).unwrap_or(0) //get player one symbol
}
#[ink(message)]
pub fn get_board(&self) -> Vec<u64> {
//read and return board as array
let board = &self.board;
board.to_vec()
}
A maioria dos métodos definidos acima não é autoexplicativa, mas falarei brevemente sobre cada um deles para dar uma compreensão clara de quais valores eles devem produzir.
- get_stake_amount()
Isso é usado para obter o valor que os jogadores apostam no jogo para que possam jogar. - get_last_winner()
Isso é usado para obter o endereço do jogador que ganhou o jogo pela última vez. - get_current_turn()
Isso é usado para obter o jogador de quem é a vez. - get_staking_token()
Isso é usado para obter o endereço do contrato de token de aposta que os jogadores usarão como licitação. - get_player_one_stake()
Isso é usado para obter o número total de tokens apostados pelo primeiro jogador. - get_player_two_stake()
Isso é usado para obter o número total de tokens apostados pelo outro jogador. - get_player_one()
Isso é usado para obter o endereço da carteira do jogador. - get_player_two()
Isso é usado para obter o endereço da carteira do outro jogador. - get_player_one_symbol()
Isso é usado para obter o símbolo do jogador. - get_player_two_symbol()
Isso é usado para obter o símbolo do segundo jogador. - get_board()
Isso retorna uma matriz que representa o estado atual do jogo das células do tabuleiro de 0 a 8.
Existem métodos que usaremos internamente para ajudar a determinar valores, bem como executar ações de entrada no contrato inteligente.
Eles são:
#[inline]
pub fn _has_won(&self, symbol: u64) -> bool {
let vertical = [[0,3,6], [1,4,7], [2,5,8]];
let horizontal = [[0,1,2], [3,4,5], [6,7,8]];
let diagonal = [[0,4,8], [2,4,6]];//check vertical
let mut v_win = false;
for i in 0..=2 {
let mut count = 0;
for j in 0..=2 {
if self.board[vertical[i][j]] == symbol {
count += 1;
}
}
if count == 3 {
v_win = true;
break;
}
}
//check horizontal
let mut h_win = false;
for i in 0..=2 {
let mut count = 0;
for j in 0..=2 {
if self.board[horizontal[i][j]] == symbol {
count += 1;
}
}
if count == 3 {
h_win = true;
break;
}
}
//check diagonal
let mut d_win = false;
for i in 0..=1 {
let mut count = 0;
for j in 0..=2 {
if self.board[diagonal[i][j]] == symbol {
count += 1;
}
}
if count == 3 {
d_win = true;
break;
}
}
if v_win == true || h_win == true || d_win == true {
true
}
else {
false
}
}
#[inline]
pub fn _clear_board(&mut self) {
let board = vec![0; 9];
self.board = board;
}
#[inline]
pub fn _is_cell_empty(&self, cell: u64) -> bool {
if self.board[usize::try_from(cell).unwrap()] == 0 {
true
} else {
false
}
}
#[inline]
pub fn _is_board_filled(&self) -> bool {
let mut filled_cells = 0;
let board = &self.board;
for cell in 0..=8 {
if board[usize::try_from(cell).unwrap()] != 0 {
filled_cells += 1;
}
}
if filled_cells == 9 {
true
} else {
false
}
}
#[inline]
pub fn _reward_winner(&mut self, account: AccountId) {
let total_stakes = PSP22Ref::balance_of(&self.staking_token, Self::env().account_id()); //get total stakes
PSP22Ref::transfer(
&self.staking_token,
account,
total_stakes,
ink_prelude::vec![],
); //transfer everything to the winner
self.stakes.insert(self.player_one, &0);
self.stakes.insert(self.player_two, &0);
}
#[inline]
pub fn _refund_tokens(&mut self) {
let total_stakes = PSP22Ref::balance_of(&self.staking_token, Self::env().account_id()); //get total stakes
let per_player = total_stakes / 2;
PSP22Ref::transfer(
&self.staking_token,
self.player_one,
per_player,
ink_prelude::vec![],
); //transfer half to player one
PSP22Ref::transfer(
&self.staking_token,
self.player_two,
per_player,
ink_prelude::vec![],
); //transfer half to player two
self.stakes.insert(self.player_one, &0);
self.stakes.insert(self.player_two, &0);
}
Explicarei brevemente o que os métodos auxiliares integrados acima fazem, caso você não tenha uma ideia clara sobre eles.
- _won(símbolo: u64)
Este método é usado para determinar se um determinado símbolo é um símbolo vencedor. Retorna um valor booleano (verdadeiro ou falso). Usado para verificar o vencedor para cada movimento feito. - _clear_board()
Este método é usado para limpar o tabuleiro (definir todas as células como 0 ou vazias, conforme o caso). - _is_cell_empty()
Isso é usado para determinar se uma determinada célula está vazia (tem um valor de 0). - _is_board_fill()
Isso é usado para determinar se o tabuleiro foi preenchido (aplica-se para verificar se não há vencedor na rodada ativa). - _reward_winner(endereço: AccountId)
Este método é usado para passar todos os tokens disponíveis para o endereço fornecido como um argumento. - _refund_tokens()
Este método é utilizado para retornar todos os tokens (aplica-se ao cenário em que há empate na rodada ativa).
Este é o aspecto mais importante de um contrato inteligente, pois pode ter consequências terríveis se não for escrito com cuidado.
Existem dois métodos que determinam um jogo bem-sucedido:
- stake_tokens()
- jogar (célula:u64)
Vou começar com o primeiro (como deveria ser :)).
stake_tokens()
#[ink(message)]
pub fn stake_tokens(&mut self) {
let player = self.env().caller(); //get caller address
let stakes = self.stakes.get(player).unwrap_or(0); //get stake if existentassert!(player == self.player_one || player == self.player_two); //Caller must be player one or two
if stakes > 0 {
panic!("Already staked for this round")
} //Make sure player hasn't already staked
let balance = PSP22Ref::balance_of(&self.staking_token, player); //get user balance of token
let allowance =
PSP22Ref::allowance(&self.staking_token, player, Self::env().account_id()); //get spending allowance contract has to player
assert!(balance > self.stake_amount); //balance must be greater than stake amount
assert!(allowance > self.stake_amount); //allowance must be greater than stake amount
//Transfer stake amount from caller (player) to contract
PSP22Ref::transfer_from_builder(
&self.staking_token,
self.env().caller(),
Self::env().account_id(),
self.stake_amount,
ink_prelude::vec![],
)
.call_flags(CallFlags::default().set_allow_reentry(true))
.fire()
.expect("Transfer failed")
.expect("Transfer failed");
self.stakes.insert(player, &self.stake_amount); //Add stake amount to user stake
}
Do bloco de código acima, pode-se concluir que este método é usado para depositar os tokens de aposta (fazer uma aposta) para a próxima rodada.
Primeiro, ele verifica se o chamador é o primeiro ou o segundo jogador e, em seguida, verifica se uma aposta já foi feita.
Também verificamos seu saldo em tokens de aposta, garantindo que corresponda ao valor da aposta e que o valor do gasto seja suficiente para que o contrato recolha o valor da aposta na carteira do jogador.
Finalmente, transferimos os tokens de aposta de sua carteira e adicionamos a aposta ao cartão de aposta do jogador.
jogar (célula:u64)
Este método recebe um argumento do tipo u64 que especifica a célula a ser reproduzida.
#[ink(message)]
pub fn play(&mut self, cell: u64) {assert!(cell <= 8);
let player = self.env().caller(); //get caller address
assert!(player == self.player_one || player == self.player_two); //caller must be player one or two
assert!(self.get_player_one_stake() > 0 && self.get_player_two_stake() > 0); //both players must have staked
let is_empty = self._is_cell_empty(cell); //check if cell is empty
assert!(is_empty == true); //cell must be empty
assert!(self.turn == player); //must be player's turn
let mut board = self.get_board();
let player_one_symbol = self.get_player_one_symbol();
let player_two_symbol = self.get_player_two_symbol();
let cell_index = usize::try_from(cell).unwrap(); //convert index to usize
board[cell_index] = self.symbols.get(player).unwrap_or(0);
self.board = board;
let player_one_won = self._has_won(player_one_symbol);
let player_two_won = self._has_won(player_two_symbol);
let mut game_over = false;
if player_one_won == true {
//player one won
self.turn = self.player_one; //set player to start next round
self._reward_winner(self.player_one);
self._clear_board(); //clear game board
self.last_winner = self.player_one; //set to last winner
game_over = true; //game is over
} else if player_two_won == true {
//player two won
self.turn = self.player_two; //set player to start next round
self._reward_winner(self.player_two);
self._clear_board(); //clear game board
self.last_winner = self.player_one; //set to last winner
game_over = true;
} else {
if self._is_board_filled() == true {
//It's a draw
self.turn = self.player_one;
self._refund_tokens(); //refund tokens because no one won
self._clear_board();
game_over = true;
}
}
if game_over == false {
if self.turn == self.player_one {
self.turn = self.player_two;
} else {
self.turn = self.player_one;
}
}
}
Fazemos muitas verificações neste método antes de deixar o jogador fazer um movimento.
Em primeiro lugar, queremos ter certeza de que o caller é um dos dois jogadores envolvidos no jogo e também garantir que ambos os jogadores apostaram suas fichas na rodada.
Em seguida, confirmamos que é a vez do jogador em questão, após o que escrevemos seu símbolo na chave apropriada na matriz de armazenamento do tabuleiro.
Verificamos as condições de vitória e empate (verificadas para cada movimento feito).
Se um jogador vencer, ele recebe todo o seu dinheiro, o placar é zerado e ele se torna o próximo jogador (joga no próximo turno).
Se o jogo terminar empatado, o tabuleiro também é limpo, as fichas são devolvidas para os dois lados e uma nova rodada começa.
Acabamos! 🔥
Dito isso, mereço uma taça de vinho gelada. Espero que tenha gostado de ler e/ou praticar este tutorial 💚.
Aqui está o código-fonte completo (embora eu o tenha formatado :)):
#![cfg_attr(not(feature = "std"), no_std)]use ink_lang as ink;
#[cfg(not(feature = "ink-as-dependency"))]
#[ink::contract]
pub mod tic_tac_toe {
use ink_prelude::vec;
use ink_prelude::vec::Vec;
use ink_storage::traits::SpreadAllocate;
use openbrush::contracts::traits::psp22::PSP22Ref;
use ink_env::CallFlags;
#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct TicTacToe {
board: Vec<u64>, //0 to 8 cells
turn: AccountId,
symbols: ink_storage::Mapping<AccountId, u64>,
player_one: AccountId,
player_two: AccountId,
staking_token: AccountId,
stake_amount: Balance,
stakes: ink_storage::Mapping<AccountId, Balance>,
last_winner: AccountId,
}
impl TicTacToe {
/// Creates a new instance of this contract.
#[ink(constructor)]
pub fn new(
player_one: AccountId,
player_two: AccountId,
player_one_symbol: u64,
player_two_symbol: u64,
staking_token: AccountId,
stake_amount: Balance,
) -> Self {
let me = ink_lang::utils::initialize_contract(|contract: &mut Self| {
let board = vec![0; 9]; //empty array
contract.board = board; //set board to empty state
contract.staking_token = staking_token; //set staking token
contract.stake_amount = stake_amount; //set stake amount
assert!(player_one != player_two); //addresses must not be the same
assert!(player_one_symbol != player_two_symbol); //symbols must be distinct
assert!(
(player_one_symbol == 1 || player_one_symbol == 2)
&& (player_two_symbol == 1 || player_two_symbol == 2)
); //symbols must be either 1 or 2
contract.player_one = player_one; //set player one address
contract.player_two = player_two; //set player two address
contract.symbols.insert(player_one, &player_one_symbol); //set player one symbol
contract.symbols.insert(player_two, &player_two_symbol); //set player two symbol
contract.turn = player_one; //initialize turn to player one
});
me
}
#[ink(message)]
pub fn get_stake_amount(&self) -> Balance {
self.stake_amount //amount to be staked in game
}
#[ink(message)]
pub fn get_last_winner(&self) -> AccountId {
self.last_winner //address of most recent winner
}
#[ink(message)]
pub fn get_current_turn(&self) -> AccountId {
self.turn //who is meant to play?
}
#[ink(message)]
pub fn get_staking_token(&self) -> AccountId {
self.staking_token //get address of staking token smart contract
}
#[ink(message)]
pub fn get_player_two_stake(&self) -> Balance {
self.stakes.get(self.player_two).unwrap_or(0) //get total amount of tokens staked by player two
}
#[ink(message)]
pub fn get_player_one_stake(&self) -> Balance {
self.stakes.get(self.player_one).unwrap_or(0) //get total amount of tokens staked by player one
}
#[ink(message)]
pub fn get_player_one(&self) -> AccountId {
self.player_one //get player one address
}
#[ink(message)]
pub fn get_player_two(&self) -> AccountId {
self.player_two //get player two address
}
#[ink(message)]
pub fn get_player_two_symbol(&self) -> u64 {
self.symbols.get(self.player_two).unwrap_or(0) //get player two symbol
}
#[ink(message)]
pub fn get_player_one_symbol(&self) -> u64 {
self.symbols.get(self.player_one).unwrap_or(0) //get player one symbol
}
#[ink(message)]
pub fn get_board(&self) -> Vec<u64> {
//read and return board as array
let board = &self.board;
board.to_vec()
}
#[ink(message)]
pub fn stake_tokens(&mut self) {
let player = self.env().caller(); //get caller address
let stakes = self.stakes.get(player).unwrap_or(0); //get stake if existent
assert!(player == self.player_one || player == self.player_two); //Caller must be player one or two
if stakes > 0 {
panic!("Already staked for this round")
} //Make sure player hasn't already staked
let balance = PSP22Ref::balance_of(&self.staking_token, player); //get user balance of token
let allowance =
PSP22Ref::allowance(&self.staking_token, player, Self::env().account_id()); //get spending allowance contract has to player
assert!(balance > self.stake_amount); //balance must be greater than stake amount
assert!(allowance > self.stake_amount); //allowance must be greater than stake amount
//Transfer stake amount from caller (player) to contract
PSP22Ref::transfer_from_builder(
&self.staking_token,
self.env().caller(),
Self::env().account_id(),
self.stake_amount,
ink_prelude::vec![],
)
.call_flags(CallFlags::default().set_allow_reentry(true))
.fire()
.expect("Transfer failed")
.expect("Transfer failed");
self.stakes.insert(player, &self.stake_amount); //Add stake amount to user stake
}
#[inline]
pub fn _has_won(&self, symbol: u64) -> bool {
let vertical = [[0, 3, 6], [1, 4, 7], [2, 5, 8]];
let horizontal = [[0, 1, 2], [3, 4, 5], [6, 7, 8]];
let diagonal = [[0, 4, 8], [2, 4, 6]];
//check vertical
let mut v_win = false;
for i in 0..=2 {
let mut count = 0;
for j in 0..=2 {
if self.board[vertical[i][j]] == symbol {
count += 1;
}
}
if count == 3 {
v_win = true;
break;
}
}
//check horizontal
let mut h_win = false;
for i in 0..=2 {
let mut count = 0;
for j in 0..=2 {
if self.board[horizontal[i][j]] == symbol {
count += 1;
}
}
if count == 3 {
h_win = true;
break;
}
}
//check diagonal
let mut d_win = false;
for i in 0..=1 {
let mut count = 0;
for j in 0..=2 {
if self.board[diagonal[i][j]] == symbol {
count += 1;
}
}
if count == 3 {
d_win = true;
break;
}
}
if v_win == true || h_win == true || d_win == true {
true
} else {
false
}
}
#[inline]
pub fn _clear_board(&mut self) {
let board = vec![0; 9];
self.board = board;
}
#[inline]
pub fn _is_cell_empty(&self, cell: u64) -> bool {
if self.board[usize::try_from(cell).unwrap()] == 0 {
true
} else {
false
}
}
#[inline]
pub fn _is_board_filled(&self) -> bool {
let mut filled_cells = 0;
let board = &self.board;
for cell in 0..=8 {
if board[usize::try_from(cell).unwrap()] != 0 {
filled_cells += 1;
}
}
if filled_cells == 9 {
true
} else {
false
}
}
#[inline]
pub fn _reward_winner(&mut self, account: AccountId) {
let total_stakes = PSP22Ref::balance_of(&self.staking_token, Self::env().account_id()); //get total stakes
PSP22Ref::transfer(
&self.staking_token,
account,
total_stakes,
ink_prelude::vec![],
); //transfer everything to the winner
self.stakes.insert(self.player_one, &0);
self.stakes.insert(self.player_two, &0);
}
#[inline]
pub fn _refund_tokens(&mut self) {
let total_stakes = PSP22Ref::balance_of(&self.staking_token, Self::env().account_id()); //get total stakes
let per_player = total_stakes / 2;
PSP22Ref::transfer(
&self.staking_token,
self.player_one,
per_player,
ink_prelude::vec![],
); //transfer half to player one
PSP22Ref::transfer(
&self.staking_token,
self.player_two,
per_player,
ink_prelude::vec![],
); //transfer half to player two
self.stakes.insert(self.player_one, &0);
self.stakes.insert(self.player_two, &0);
}
#[ink(message)]
pub fn play(&mut self, cell: u64) {
assert!(cell <= 8);
let player = self.env().caller(); //get caller address
assert!(player == self.player_one || player == self.player_two); //caller must be player one or two
assert!(self.get_player_one_stake() > 0 && self.get_player_two_stake() > 0); //both players must have staked
let is_empty = self._is_cell_empty(cell); //check if cell is empty
assert!(is_empty == true); //cell must be empty
assert!(self.turn == player); //must be player's turn
let mut board = self.get_board();
let player_one_symbol = self.get_player_one_symbol();
let player_two_symbol = self.get_player_two_symbol();
let cell_index = usize::try_from(cell).unwrap(); //convert index to usize
board[cell_index] = self.symbols.get(player).unwrap_or(0);
self.board = board;
let player_one_won = self._has_won(player_one_symbol);
let player_two_won = self._has_won(player_two_symbol);
let mut game_over = false;
if player_one_won == true {
//player one won
self.turn = self.player_one; //set player to start next round
self._reward_winner(self.player_one);
self._clear_board(); //clear game board
self.last_winner = self.player_one; //set to last winner
game_over = true; //game is over
} else if player_two_won == true {
//player two won
self.turn = self.player_two; //set player to start next round
self._reward_winner(self.player_two);
self._clear_board(); //clear game board
self.last_winner = self.player_one; //set to last winner
game_over = true;
} else {
if self._is_board_filled() == true {
//It's a draw
self.turn = self.player_one;
self._refund_tokens(); //refund tokens because no one won
self._clear_board();
game_over = true;
}
}
if game_over == false {
if self.turn == self.player_one {
self.turn = self.player_two;
} else {
self.turn = self.player_one;
}
}
}
}
}
Se você gostou deste artigo e/ou achou útil, siga-me no Twitter como @EOttoho fique atento aos meus movimentos 😎. Para acessar o código-fonte completo no GitHub, siga este link. Glória.
Sinta-se à vontade para bifurcar o repositório e me enviar um PR a qualquer momento.
Para implantar um contrato inteligente no Aleph Zero Testnet com uma interface intrigante e suave, você pode seguir este link.
Se você chegou a este ponto neste artigo, você merece alguns tokens $AZERO 🙂
Estou feliz que você está aqui o tempo todo.
Novo na negociação? Experimente bots de negociação de criptomoedas ou cópia de negociação
[ad_2]
Source link