Main Library

This is the lib.rs file. We will skip some of the explanations already available in this website demonstrating the contract basics and focus more on the specifics.

Recall the Place that we define before. This struct defines the "place" where the board is held, and the participants (accounts) which participate in this game.

use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::{UnorderedMap, Vector};
use near_sdk::{env, near_bindgen, AccountId, Balance, PanicOnDefault};

pub mod board;
pub use crate::board::*;

#[global_allocator]
static ALLOC: near_sdk::wee_alloc::WeeAlloc<'_> = near_sdk::wee_alloc::WeeAlloc::INIT;

#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
pub struct Place {
    pub account_indices: UnorderedMap<AccountId, u32>,
    pub board: board::PixelBoard,
    pub accounts: Vector<Account>,
}

Let's get into the implementation.

use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::{UnorderedMap, Vector};
use near_sdk::{env, near_bindgen, AccountId, Balance, PanicOnDefault};

pub mod account;
pub use crate::account::*;

pub mod board;
pub use crate::board::*;

#[global_allocator]
static ALLOC: near_sdk::wee_alloc::WeeAlloc<'_> = near_sdk::wee_alloc::WeeAlloc::INIT;

#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
pub struct Place {
    pub account_indices: UnorderedMap<AccountId, u32>,
    pub board: board::PixelBoard,
    pub accounts: Vector<Account>,
}

#[near_bindgen]
impl Place {
    #[init]
    pub fn new() -> Self {
      assert!(!env::state_exists(), "Already initialized");
      let mut place = Self {
        account_indices: UnorderedMap::new(b"i".to_vec()),
        board: PixelBoard::new(),
        accounts: Vector::new(b"a".to_vec()),
      };

      let mut account = place.get_account_by_id(env::current_account_id());
      account.num_pixels = TOTAL_NUM_PIXELS;
      place.save_account(&account);

      place
    }

    #[payable]
    pub fn buy_tokens(&mut self) {
      unimplemented!();
    }

    pub fn draw(&mut self, pixels: Vec<SetPixelRequest>) {
      let mut account = self.get_account_by_id(env::predecessor_account_id());
      let new_pixels = pixels.len() as u32;
      account.charge(new_pixels);

      let mut old_owners = self.board.set_pixels(account.account_index, &pixels);
      let replaced_pixels = old_owners.remove(&account.account_index).unwrap_or(0);
      account.num_pixels += new_pixels - replaced_pixels;
      self.save_account(&account);

      for (account_index, num_pixels) in old_owners {
        let mut account = self.get_account_by_index(account_index).unwrap();
        account.num_pixels -= num_pixels;
        self.save_account(&account);
      }
    }
}


#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {
    use super::*;

    use near_sdk::{testing_env, MockedBlockchain, VMContext};

    pub fn get_context(block_timestamp: u64, is_view: bool) -> VMContext {
      VMContext {
        current_account_id: "place.meta".to_string(),
        signer_account_id: "place.meta".to_string(),
        signer_account_pk: vec![0, 1, 2],
        predecessor_account_id: "place.meta".to_string(),
        input: vec![],
        block_index: 1,
        block_timestamp,
        epoch_height: 1,
        account_balance: 10u128.pow(26),
        account_locked_balance: 0,
        storage_usage: 10u64.pow(6),
        attached_deposit: 0,
        prepaid_gas: 300 * 10u64.pow(12),
        random_seed: vec![0, 1, 2],
        is_view,
        output_data_receivers: vec![],
      }
    }

    #[test]
    fn test_new() {
      let mut context = get_context(3_600_000_000_000, false);
      testing_env!(context.clone());
      let contract = Place::new();

      context.is_view = true;
      testing_env!(context.clone());
      assert_eq!(
        contract.get_pixel_cost().0,
        PIXEL_COST
      );
      assert_eq!(
        contract.get_line_versions(),
        vec![0u32; BOARD_HEIGHT as usize]
      )
    }
}

As usual, we have the new function to perform initialization. We also start by assigning all pixels to a single account (perhaps the account which the contract is deployed to).

And the ability to draw is another important aspect here. Though, note that buy_tokens is unimplemented; you can check Berry club contract for more information if you want. That's the ability to buy fungible tokens, which first requires you to write a contract for fungible tokens, then port it in here. In Berryclub, these are cucumber, avocado, and bananas (may update in the future).

The draw logic includes replacing the old owner for each Pixel to its new owner.

Note: if at this point things won't run well, especially with u16 error, please replace all of them with u32.

Testing

Of course we need testing. Here we'll look at a simple unit test, to test the creation of new board is successful.

use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::{UnorderedMap, Vector};
use near_sdk::{env, near_bindgen, AccountId, Balance, PanicOnDefault};

pub mod account;
pub use crate::account::*;

pub mod board;
pub use crate::board::*;

#[global_allocator]
static ALLOC: near_sdk::wee_alloc::WeeAlloc<'_> = near_sdk::wee_alloc::WeeAlloc::INIT;

#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
pub struct Place {
    pub account_indices: UnorderedMap<AccountId, u32>,
    pub board: board::PixelBoard,
    pub accounts: Vector<Account>,
}

#[near_bindgen]
impl Place {
    #[init]
    pub fn new() -> Self {
      assert!(!env::state_exists(), "Already initialized");
      let mut place = Self {
        account_indices: UnorderedMap::new(b"i".to_vec()),
        board: PixelBoard::new(),
        accounts: Vector::new(b"a".to_vec()),
      };

      let mut account = place.get_account_by_id(env::current_account_id());
      account.num_pixels = TOTAL_NUM_PIXELS;
      place.save_account(&account);

      place
    }

    #[payable]
    pub fn buy_tokens(&mut self) {
      unimplemented!();
    }

    pub fn draw(&mut self, pixels: Vec<SetPixelRequest>) {
      let mut account = self.get_account_by_id(env::predecessor_account_id());
      let new_pixels = pixels.len() as u32;
      account.charge(new_pixels);

      let mut old_owners = self.board.set_pixels(account.account_index, &pixels);
      let replaced_pixels = old_owners.remove(&account.account_index).unwrap_or(0);
      account.num_pixels += new_pixels - replaced_pixels;
      self.save_account(&account);

      for (account_index, num_pixels) in old_owners {
        let mut account = self.get_account_by_index(account_index).unwrap();
        account.num_pixels -= num_pixels;
        self.save_account(&account);
      }
    }
}


#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {
    use super::*;

    use near_sdk::{testing_env, MockedBlockchain, VMContext};

    pub fn get_context(block_timestamp: u64, is_view: bool) -> VMContext {
      VMContext {
        current_account_id: "place.meta".to_string(),
        signer_account_id: "place.meta".to_string(),
        signer_account_pk: vec![0, 1, 2],
        predecessor_account_id: "place.meta".to_string(),
        input: vec![],
        block_index: 1,
        block_timestamp,
        epoch_height: 1,
        account_balance: 10u128.pow(26),
        account_locked_balance: 0,
        storage_usage: 10u64.pow(6),
        attached_deposit: 0,
        prepaid_gas: 300 * 10u64.pow(12),
        random_seed: vec![0, 1, 2],
        is_view,
        output_data_receivers: vec![],
      }
    }

    #[test]
    fn test_new() {
      let mut context = get_context(3_600_000_000_000, false);
      testing_env!(context.clone());
      let contract = Place::new();

      context.is_view = true;
      testing_env!(context.clone());
      assert_eq!(
        contract.get_pixel_cost().0,
        PIXEL_COST
      );
      assert_eq!(
        contract.get_line_versions(),
        vec![0u32; BOARD_HEIGHT as usize]
      )
    }
}

End Note

Of course, the listings aren't totally correct. The only tested ones are the final ones, which is listing-13. If you want to read the working ones (no errors), read listing-13. Otherwise, go to the original Github repo for the code.

Next, we'll take a look at frontend code.

Resources