Skip to main content

Assignment

Task Description​

In this homework you are to write the Pebbles Game. The games rules are the following:

  • There are two players: User and Program. The first player is chosen randomly.
  • The game starts with NN pebbles (e.g., N=15N = 15).
  • On the player's turn they must remove from 11 to KK pebbles (e.g., if K=2K = 2, then the player removes 11 or 22 pebbles per turn).
  • The player who takes last pebble(s) is the winner.

Project Structure​

It is necessary to make two crates: pebbles-game for the program and pebbles-game-io for data structures.

The directory structure should be the following:

pebbles-game
├── io
│ ├── src
│ │ └── lib.rs
│ └── Cargo.toml
├── src
│ └── lib.rs
├── tests
│ └── basic.rs
├── Cargo.lock
├── Cargo.toml
└── build.rs

Types Definition​

The pebbles-game-io will contains type definitions for input, output, and internal state data.

Its Cargo.toml manifest will be the following:

io/Cargo.toml
[package]
name = "pebbles-game-io"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1" }
gmeta = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1" }
parity-scale-codec = { version = "3", default-features = false }
scale-info = { version = "2", default-features = false }

Let's explore types used.

  • When initialising the game, it is necessary to pass some initial information. For example, the number of pebbles (NN), maximum pebbles to be removed per turn (KK), difficulty level.

    io/src/lib.rs
    #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)]
    pub struct PebblesInit {
    pub difficulty: DifficultyLevel,
    pub pebbles_count: u32,
    pub max_pebbles_per_turn: u32,
    }

    #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)]
    pub enum DifficultyLevel {
    #[default]
    Easy,
    Hard,
    }
  • It needs to send actions message for every User's move and receive some event from the program. The action can be a turn with some count of pebbles to be removed or the give up. Also, there is a restart action than resets the game state .

    io/src/lib.rs
    #[derive(Debug, Clone, Encode, Decode, TypeInfo)]
    pub enum PebblesAction {
    Turn(u32),
    GiveUp,
    Restart {
    difficulty: DifficultyLevel,
    pebbles_count: u32,
    max_pebbles_per_turn: u32,
    },
    }

    And the event reflects the game state after the User's move: either pebbles count removed by the Program or the end of game with the information about the winner.

    io/src/lib.rs
    #[derive(Debug, Clone, Encode, Decode, TypeInfo)]
    pub enum PebblesEvent {
    CounterTurn(u32),
    Won(Player),
    }

    #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)]
    pub enum Player {
    #[default]
    User,
    Program,
    }
  • Internal game state should keep all information related to the current state of the game. Some information is set during initialization, the first player is chosen randomly, some data are change during the game.

    io/src/lib.rs
    #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)]
    pub struct GameState {
    pub pebbles_count: u32,
    pub max_pebbles_per_turn: u32,
    pub pebbles_remaining: u32,
    pub difficulty: DifficultyLevel,
    pub first_player: Player,
    pub winner: Option<Player>,
    }

Finally, the metadata to be used by the IDEA portal.

io/src/lib.rs
impl Metadata for PebblesMetadata {
type Init = In<PebblesInit>;
type Handle = InOut<PebblesAction, PebblesEvent>;
type State = Out<GameState>;
type Reply = ();
type Others = ();
type Signal = ();
}

The Homework Assignment​

  1. Write init() function that:

    • Receives PebblesInit using the msg::load function;
    • Checks input data for validness;
    • Chooses the first player using the exec::random function;
    • Processes the first turn if the first player is Program.
    • Fills the GameState structure.
  2. Write the handle() function that:

    • Receives PebblesAction using msg::load function;
    • Checks input data for validness;
    • Processes the User's turn and check whether they win;
    • Processes the Program turn and check whether it wins;
    • Send a message to the user with the correspondent PebblesEvent;
  3. Write the state() function that returns the GameState structure using the msg::reply function.

Additional Information​

There are two difficulty levels in the game: DifficultyLevel::Easy and DifficultyLevel::Hard. Program should choose the pebbles count to be removed randomly at the easy level, and find the best pebbles count (find a winning strategy) at the hard level.

Use the following helper function to get a random 32-bit number:

fn get_random_u32() -> u32 {
let salt = msg::id();
let (hash, _num) = exec::random(salt.into()).expect("get_random_u32(): random call failed");
u32::from_le_bytes([hash[0], hash[1], hash[2], hash[3]])
}

Testing​

You are to cover program initialization and all actions by tests using the gtest crate.

  • Check whether the game initialized correctly.
  • Check all program strategies (you may split the get_random_u32() function into two separated implementations for #[cfg(test)] and #[cfg(not(test))] environments).
  • Check negative scenarios and invalid input data processing.

Afterword

  • The homework should be done as the PR in the GitHub repository.
  • You are to upload the Wasm binary to the Vara Network Testnet and send its address.
  • If you encounter challenges in the development of a project, it is advisable to refer to the Gear Wiki for guidance. It provides comprehensive instructions for developing programs on Gear.