diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b77614..32c97f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,7 @@ on: branches: [main] push: branches: [main] + workflow_dispatch: env: CARGO_TERM_COLOR: always @@ -28,10 +29,12 @@ jobs: cargo fmt --all --check cargo clippy --all-targets -- -D warnings -A unused-imports cargo b -r + cargo t - name: Session Check and test run: | cd session cargo fmt --all --check cargo clippy --all-targets -- -D warnings -A unused-imports - cargo b -r \ No newline at end of file + cargo b -r + cargo t \ No newline at end of file diff --git a/1720184706536.jpg b/1720184706536.jpg new file mode 100644 index 0000000..46fa4b5 Binary files /dev/null and b/1720184706536.jpg differ diff --git a/1720185307791.jpg b/1720185307791.jpg new file mode 100644 index 0000000..dc85c73 Binary files /dev/null and b/1720185307791.jpg differ diff --git a/1720185876769.jpg b/1720185876769.jpg new file mode 100644 index 0000000..643e486 Binary files /dev/null and b/1720185876769.jpg differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f36cb4b --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Task Description +For this assignment, you are tasked with writing the Wordle game. + +Wordle is a captivating word-guessing game that has become popular for its simplicity and addictive gameplay. The goal is to guess a hidden word within a limited number of attempts. + +The game board consists of six rows, each allowing the player to input a word. Upon entering a word, the player receives feedback indicating the presence of letters in the hidden word and their correct positions. If a letter is present but in the wrong position, it may be highlighted in a different color or marked with a distinct symbol. Players utilize these clues to deduce the correct letters and their placements. + +The game challenges players to guess the word with the fewest attempts, under time constraints and limited attempts, adding excitement and suspense. + +# Project Structure +Break down your Wordle game into two programs for a more engaging and flexible experience. The Wordle program will handle the core functionalities, such as selecting a random word from a list and evaluating guesses. The Game Session program will manage user interactions, keep track of the game state, and enforce time constraints. This division aims to create a modular, flexible system that enhances the gaming experience. + +1. Description of the Wordle program: + +Contains "start the game" and "check the word" functions. +A word bank exists within the program for selecting a random word at the game's start. +"Start the game" function initiates the game and selects a random word. +"Check the word" function assesses the player's guess against the hidden word, providing feedback on correct letter positions. + +2. Description of the Game Session program: + +Manages interactions with the Wordle program and oversees the gameplay. +Tracks previous responses and the number of attempts. +Monitors the elapsed time since the game started to manage time constraints and events. + +3. Interaction Between the Programs: + +The user initiates the game by sending a message to the Game Session program. +The Game Session program invokes the Wordle program's "start the game" function. +The user submits their guesses to the Game Session program, which forwards them to the Wordle program's "check the word" function. +The Wordle program returns feedback on the guess's accuracy and letter positions. +The Game Session program analyzes the result, tracking attempts and time. + +4. Key Implementation Aspects: + +The Game Session program requires mechanisms to store data about previous moves and track time. +Efficient interaction with the Wordle program through data exchange and response handling is crucial. + +![Alt text](1720185876769.jpg) + +# Deployment and Use +https://idea.gear-tech.io/ +wss://testnet.vara.network + +aiqubits_wordle_game program id +0x98de5336075b2550ff6ee7b3ec4b72b4856412a5df580f4f5118fcb0e87f7a25 + +https://idea.gear-tech.io/programs/0x98de5336075b2550ff6ee7b3ec4b72b4856412a5df580f4f5118fcb0e87f7a25?node=wss%3A%2F%2Ftestnet.vara.network + +aiqubits_session_game program id +0xdb3552cb90e74ad9bc15f4804830e7e01a34bea4f4c2093eb4657ffdecb1a8b6 + +https://idea.gear-tech.io/programs/0xdb3552cb90e74ad9bc15f4804830e7e01a34bea4f4c2093eb4657ffdecb1a8b6?node=wss%3A%2F%2Ftestnet.vara.network + +![Alt text](1720184706536.jpg) +![Alt text](1720185307791.jpg) diff --git a/session/Cargo.lock b/session/Cargo.lock index d19fcb3..6b0eb5c 100644 --- a/session/Cargo.lock +++ b/session/Cargo.lock @@ -3876,6 +3876,17 @@ dependencies = [ "serde", ] +[[package]] +name = "session-game-io" +version = "0.1.0" +dependencies = [ + "enum-iterator 2.1.0", + "gmeta", + "gstd", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "session-proxy" version = "0.1.0" @@ -3886,6 +3897,7 @@ dependencies = [ "gtest", "parity-scale-codec", "scale-info", + "session-game-io", "wordle-game-io", ] diff --git a/session/Cargo.toml b/session/Cargo.toml index f5c53cc..8501e8f 100644 --- a/session/Cargo.toml +++ b/session/Cargo.toml @@ -4,19 +4,22 @@ version = "0.1.0" edition = "2021" [dependencies] -gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1",features = ["debug"] } +gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1", features = ["debug"] } parity-scale-codec = { version = "3", default-features = false } scale-info = { version = "2", default-features = false } +session-game-io.path = "io" wordle-game-io.path = "../wordle/io" [build-dependencies] gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1" } +session-game-io.path = "io" wordle-game-io.path = "../wordle/io" [dev-dependencies] -gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1" } +gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1", features = ["debug"] } gmeta = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1" } gtest = { 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 } +session-game-io.path = "io" wordle-game-io.path = "../wordle/io" diff --git a/session/build.rs b/session/build.rs index 6836d02..0ce20e6 100644 --- a/session/build.rs +++ b/session/build.rs @@ -1,3 +1,5 @@ +use session_game_io::WordleMetadata; + fn main() { - gear_wasm_builder::build(); + gear_wasm_builder::build_with_metadata::(); } diff --git a/session/io/Cargo.toml b/session/io/Cargo.toml new file mode 100644 index 0000000..17a926c --- /dev/null +++ b/session/io/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "session-game-io" +version = "0.1.0" +edition = "2021" + +[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 } +enum-iterator = "2.1.0" diff --git a/session/io/src/lib.rs b/session/io/src/lib.rs new file mode 100644 index 0000000..b65fe7e --- /dev/null +++ b/session/io/src/lib.rs @@ -0,0 +1,83 @@ +#![no_std] +use gmeta::{InOut, Metadata}; +use gstd::{collections::HashMap, prelude::*, ActorId, MessageId}; + +// the metadata to be used by the [IDEA](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Ftestnet.vara.network) portal. +#[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)] +pub struct WordleMetadata; + +impl Metadata for WordleMetadata { + type Init = (); + type Handle = InOut; + type Others = (); + type Reply = (); + type Signal = (); + type State = InOut; +} + +// Id of a User initiating the action is Key, Value is Session. +// pub type State = Vec<(ActorId, Session)>; +pub type State = HashMap; + +type SentMessageId = MessageId; +type OriginalMessageId = MessageId; + +#[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)] +#[codec(crate = gstd::codec)] +#[scale_info(crate = gstd::scale_info)] +pub struct Session { + // pub target_program_id: ActorId, // target program address + pub msg_ids: (SentMessageId, OriginalMessageId), // tuple containing the identifier of a message sent to a Target program, the identifier of a message current program. + pub session_status: SessionStatus, + pub tries_number: u8, +} + +#[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)] +#[codec(crate = gstd::codec)] +#[scale_info(crate = gstd::scale_info)] +pub enum SessionStatus { + None, + GameStarted, + Waiting, + GameOver(Outcome), + WordChecked { + user: ActorId, + correct_positions: Vec, + contained_in_word: Vec, + }, + InvalidWord, + NoReplyReceived, +} + +#[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)] +#[codec(crate = gstd::codec)] +#[scale_info(crate = gstd::scale_info)] +pub enum Action { + StartGame, + CheckWord(String), + CheckGameStatus, +} + +#[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)] +#[codec(crate = gstd::codec)] +#[scale_info(crate = gstd::scale_info)] +pub enum Outcome { + Win, + Lose, +} + +#[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)] +#[codec(crate = gstd::codec)] +#[scale_info(crate = gstd::scale_info)] +pub enum StateQuery { + All, + Player(ActorId), +} + +#[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)] +#[codec(crate = gstd::codec)] +#[scale_info(crate = gstd::scale_info)] +pub enum StateQueryReply { + All(Vec), + Game(Session), +} diff --git a/session/src/lib.rs b/session/src/lib.rs index b697964..16c3f5f 100644 --- a/session/src/lib.rs +++ b/session/src/lib.rs @@ -1,59 +1,162 @@ #![no_std] -use gstd::{msg, prelude::*, ActorId, MessageId}; +use gstd::{exec, msg, prelude::*, ActorId, MessageId}; +use session_game_io::*; use wordle_game_io::*; -#[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)] -struct Session { - target_program_id: ActorId, // target program address - msg_id_to_actor_id: (MessageId, ActorId), // tuple containing the identifier of a message sent to a Target program and the Id of a User initiating the action +static mut GAMES: Option = None; +static mut TARGET_PROGRAM_ID: ActorId = ActorId::zero(); + +fn is_word_lowercase(word: String) -> bool { + if word.is_empty() { + return false; + } + word.chars().all(|c| c.is_lowercase()) } -static mut SESSION: Option = None; +// Check if actorid exists and has a session, if so, the game exists, otherwise it does not exist +fn is_exist_game(user_id: &ActorId) -> bool { + unsafe { + if let Some(games) = GAMES.as_ref() { + if games.get(user_id).is_some() { + return true; + } + } + } + false +} #[no_mangle] extern "C" fn init() { // Receives and stores the Wordle program's address. let target_program_id = msg::load().expect("Unable to decode Init"); + unsafe { - SESSION = Some(Session { - target_program_id, - msg_id_to_actor_id: (MessageId::zero(), ActorId::zero()), - }); + TARGET_PROGRAM_ID = target_program_id; + GAMES = Some(State::new()); } } #[no_mangle] extern "C" fn handle() { - // Manages actions: StartGame, CheckWord, CheckGameStatus. Let's examine the functionality of each action: - // StartGame - // The program checks if a game already exists for the user; - // It sends a "StartGame" message to the Wordle program; - // Utilizes the exec::wait() or exec::wait_for() function to await a response; - // Sends a delayed message with action CheckGameStatus to monitor the game's progress (its logic will be described below); - // A reply is sent to notify the user that the game has beeen successfully started. - - // CheckWord - // Ensures that a game exists and is in the correct status; - // Validates that the submitted word length is five and is in lowercase; - // Sends a "CheckWord" message to the Wordle program; - // Utilizes the exec::wait() or exec::wait_for() function to await a reply; - // Sends a reply to notify the user that the move was successful. - - // CheckGameStatus - // The game should have a time limit from its start, so a delayed message is sent to check the game status. If the game is not finished within the specified time limit, it ends the game by transitioning it to the desired status. Specify a delay equal to 200 blocks (10 minutes) for the delayed message. - - let action: WordleAction = msg::load().expect("Unable to decode "); - let session = unsafe { SESSION.as_mut().expect("The session is not initialized") }; - let msg_id = - msg::send(session.target_program_id, action, 0).expect("Error in sending a message"); - session.msg_id_to_actor_id = (msg_id, msg::source()); - msg::reply( - WordleEvent::GameStarted { - user: msg::source(), - }, - 0, - ) - .expect("Error in sending a reply"); + let action: Action = msg::load().expect("Unable to decode `Action`"); + + let user_id = msg::source(); + + let games = unsafe { GAMES.as_mut().expect("The session is not initialized") }; + + if !is_exist_game(&user_id) { + let session: Session = Session { + msg_ids: (MessageId::zero(), MessageId::zero()), + session_status: SessionStatus::None, + tries_number: 0, + }; + + games.insert(user_id, session); + } + + match action { + Action::StartGame => { + let session = games.get_mut(&user_id).expect("Unable to decode Init"); + + // let session_status = session.session_status.clone(); + if session.session_status == SessionStatus::None { + unsafe { + let msg_id = msg::send( + TARGET_PROGRAM_ID, + WordleAction::StartGame { user: user_id }, + 0, + ) + .expect("Error in sending a StartGame message"); + + session.msg_ids = (msg_id, msg::id()); + } + + msg::send_delayed(exec::program_id(), Action::CheckGameStatus, 0, 200) + .expect("Error in sending a CheckSelf Delayed message"); + + exec::wait_for(3); + } else if session.session_status == SessionStatus::GameStarted { + session.session_status = SessionStatus::Waiting; + exec::leave(); + } else if session.session_status == SessionStatus::GameOver(Outcome::Win) + || session.session_status == SessionStatus::GameOver(Outcome::Lose) + { + unsafe { + let msg_id = msg::send( + TARGET_PROGRAM_ID, + WordleAction::StartGame { user: user_id }, + 0, + ) + .expect("Error in sending a StartGame message"); + + let session = Session { + msg_ids: (msg_id, msg::id()), + session_status: SessionStatus::GameStarted, + tries_number: 0, + }; + + games.insert(user_id, session.clone()); + } + + exec::wait_for(3); + } + } + + Action::CheckWord(word) => { + let session = games.get_mut(&user_id).expect("Unable to decode Init"); + if !is_exist_game(&user_id) { + panic!("HANDLE: Action::CheckWord not_exist_game"); + } + + if word.len() != 5 && !is_word_lowercase(word.clone()) { + msg::reply(SessionStatus::InvalidWord, 0) + .expect("Error in replying a InvalidWord message"); + panic!("HANDLE: Action::CheckWord invaild vord: {:?}", word); + } + + let session_status = session.session_status.clone(); + + if session_status == SessionStatus::Waiting { + // let send_word = word.clone(); + unsafe { + let _msg_id = msg::send( + TARGET_PROGRAM_ID, + WordleAction::CheckWord { + user: user_id, + word, + }, + 0, + ) + .expect("Error in sending a CheckWord message"); + } + + exec::wait_for(20); + } + + if session_status == SessionStatus::GameOver(Outcome::Win) + || session_status == SessionStatus::GameOver(Outcome::Lose) + { + msg::reply(session_status, 0).expect("Error in replying a GameOver message"); + } + + exec::leave(); + } + Action::CheckGameStatus => { + let session = games.get_mut(&user_id).expect("Unable to decode Init"); + if session.session_status == SessionStatus::None && msg::source() == exec::program_id() + { + msg::send(user_id, SessionStatus::NoReplyReceived, 0) + .expect("Error in sending a message"); + session.session_status = SessionStatus::GameStarted; + + exec::wait_for(20); + } else { + let _ = msg::reply(session.session_status.clone(), 0); + } + + exec::leave(); + } + } } #[no_mangle] @@ -70,30 +173,104 @@ extern "C" fn handle_reply() { // Calls wake() with the identifier of the received message to acknowledge the response. - let reply_message_id = msg::reply_to().expect("Failed to query reply_to data"); - let session = unsafe { SESSION.as_mut().expect("The session is not initialized") }; - let (msg_id, actor) = session.msg_id_to_actor_id; - if reply_message_id == msg_id { - let reply: WordleEvent = msg::load().expect("Unable to decode "); - msg::send(actor, reply, 0).expect("Error in sending a message"); + let reply_to = msg::reply_to().expect("Failed to query reply_to data"); + let reply_message = msg::load().expect("Unable to decode `Event`"); + let games = unsafe { GAMES.as_mut().expect("The session is not initialized") }; + + match reply_message { + WordleEvent::GameStarted { user } => { + let session = games.get_mut(&user).expect("Failed to get session"); + + if is_exist_game(&user) && reply_to == session.msg_ids.0 { + session.session_status = SessionStatus::GameStarted; + + msg::send(user, SessionStatus::GameStarted, 0) + .expect("Error in sending a HANDLE_REPLY message"); + } + + exec::wake(session.msg_ids.1).expect("Failed to wake message"); + } + + WordleEvent::WordChecked { + user, + ref correct_positions, + ref contained_in_word, + } => { + let correct_positions_c = correct_positions.clone(); + let contained_in_word_c = contained_in_word.clone(); + let session = games.get_mut(&user).expect("Failed to get session"); + + msg::send( + user, + SessionStatus::WordChecked { + user, + correct_positions: correct_positions_c, + contained_in_word: contained_in_word_c, + }, + 0, + ) + .expect("Error in sending a HANDLE_REPLY message"); + session.tries_number += 1; + + if correct_positions.len() == 5 && contained_in_word.is_empty() { + session.session_status = SessionStatus::GameOver(Outcome::Win); + } else if session.tries_number > 3 { + session.session_status = SessionStatus::GameOver(Outcome::Lose); + } else { + session.session_status = SessionStatus::Waiting; + } + + // WAKE for wait_for + exec::wake(session.msg_ids.1).expect("Failed to wake message"); + } } } #[no_mangle] pub extern "C" fn state() { // It is necessary to implement the state() function in order to get all the information about the game. - let wordle_game = unsafe { SESSION.take().expect("Error in taking current state") }; + let query = msg::load().expect("Failed to load query"); + let state = unsafe { GAMES.take().expect("Error in taking current state") }; // Checks input data for validness - - // returns the `GameState` structure using the `msg::reply` function - msg::reply(wordle_game, 0).expect("Failed to reply state"); + let reply = match query { + StateQuery::All => StateQueryReply::All(state.into_keys().collect()), + StateQuery::Player(address) => { + let session = state.get(&address).expect("Can't find this player"); + StateQueryReply::Game(session.clone()) + } + }; + msg::reply(reply, 0).expect("Failed to reply state"); } #[cfg(test)] mod tests { - use gstd::*; + use super::*; #[test] - fn test_check_user_input() {} + fn test_is_word_lowercase() { + assert!(is_word_lowercase("hello".to_string())); + assert!(!is_word_lowercase("HELLO".to_string())); + assert!(!is_word_lowercase("HeLLo".to_string())); + assert!(!is_word_lowercase("".to_string())); + assert!(!is_word_lowercase(" ".to_string())); + assert!(!is_word_lowercase("12345".to_string())); + } + + #[test] + fn test_is_exist_game() { + let user_id = ActorId::from([1; 32]); + let mut games = State::new(); + let session = Session { + msg_ids: (MessageId::zero(), MessageId::zero()), + session_status: SessionStatus::None, + tries_number: 0, + }; + games.insert(user_id, session.clone()); + unsafe { + GAMES = Some(games); + } + assert!(is_exist_game(&user_id)); + assert!(!is_exist_game(&ActorId::from([2; 32]))); + } } diff --git a/session/tests/basic.rs b/session/tests/basic.rs index 69fa2ff..2cb6e38 100644 --- a/session/tests/basic.rs +++ b/session/tests/basic.rs @@ -1,6 +1,6 @@ use gstd::ActorId; use gtest::{Log, Program, System}; -use wordle_game_io::{WordleAction, WordleEvent}; +use session_game_io::*; const USER: u64 = 3; const TARGET_PROGRAM_ADDRESS: u64 = 2; @@ -9,50 +9,151 @@ const TARGET_PROGRAM_ADDRESS: u64 = 2; fn success_test() { // Create a new testing environment. let system = System::new(); - + system.init_logger(); // Get proxy program of the root crate with provided system. let proxy_program = Program::current(&system); // Get target program let target_program = Program::from_file( &system, - "target/wasm32-unknown-unknown/release/session_proxy.opt.wasm", + "../wordle/target/wasm32-unknown-unknown/debug/wordle_game.wasm", ); // The target program is initialized with an empty payload message let result = target_program.send_bytes(USER, []); assert!(!result.main_failed()); + let target_program_address: ActorId = TARGET_PROGRAM_ADDRESS.into(); + // The proxy program is initialized using target_program in the payload message let res = proxy_program.send(USER, target_program_address); assert!(!res.main_failed()); // Send with the message we want to receive back - let result = proxy_program.send( - USER, - WordleAction::StartGame { - user: target_program_address, - }, - ); + let result = proxy_program.send(USER, Action::StartGame); + assert!(!result.main_failed()); + println!("result:: {:?}", result.log()); + let log = Log::builder() + .source(1) + .dest(3) + .payload(SessionStatus::GameStarted); + println!("log:: {:?}", log); + assert!(result.contains(&log)); + + // User attempts to send another message to a proxy program while it is still processing the first message. It is expected that the proxy program will reply with the event `MessageAlreadySent`. + let result = proxy_program.send(USER, Action::CheckWord("hhhhh".to_owned())); + assert!(!result.main_failed()); + + let log = Log::builder() + .source(1) + .dest(3) + .payload(SessionStatus::WordChecked { + user: USER.into(), + correct_positions: vec![0], + contained_in_word: vec![1, 2, 3, 4], + }); + assert!(result.contains(&log)); + + let result = proxy_program.send(USER, Action::CheckWord("hqqqq".to_owned())); + assert!(!result.main_failed()); + + let log = Log::builder() + .source(1) + .dest(3) + .payload(SessionStatus::WordChecked { + user: USER.into(), + correct_positions: vec![0], + contained_in_word: vec![], + }); + assert!(result.contains(&log)); + + let result = proxy_program.send(USER, Action::CheckWord("qqqqq".to_owned())); assert!(!result.main_failed()); - // check that the proxy message has arrived, - // which means that the message was successfully sent to the target program let log = Log::builder() .source(1) .dest(3) - .payload(WordleEvent::GameStarted { - user: target_program_address, + .payload(SessionStatus::WordChecked { + user: USER.into(), + correct_positions: vec![], + contained_in_word: vec![], }); assert!(result.contains(&log)); - // check that the target message has arrived at the mailbox, - // which means that a reply has been received. - let mailbox = system.get_mailbox(USER); + let result = proxy_program.send(USER, Action::CheckWord("wwwww".to_owned())); + assert!(!result.main_failed()); + let log = Log::builder() .source(1) .dest(3) - .payload(WordleEvent::GameStarted { - user: target_program_address, + .payload(SessionStatus::WordChecked { + user: USER.into(), + correct_positions: vec![], + contained_in_word: vec![], }); + assert!(result.contains(&log)); + + let result = proxy_program.send(USER, Action::CheckGameStatus); + assert!(!result.main_failed()); + + let log = Log::builder() + .source(1) + .dest(3) + .payload(SessionStatus::GameOver(Outcome::Lose)); + assert!(result.contains(&log)); + + // Restart this game, Only this game is GameOver. + let result = proxy_program.send(USER, Action::StartGame); + assert!(!result.main_failed()); + + let log = Log::builder() + .source(1) + .dest(3) + .payload(SessionStatus::GameStarted); + assert!(result.contains(&log)); + + let result = proxy_program.send(USER, Action::CheckGameStatus); + assert!(!result.main_failed()); + + let log = Log::builder() + .source(1) + .dest(3) + .payload(SessionStatus::Waiting); + assert!(result.contains(&log)); + + let result = proxy_program.send(USER, Action::CheckWord("house".to_owned())); + assert!(!result.main_failed()); - assert!(mailbox.contains(&log)); + let result = proxy_program.send(USER, Action::CheckWord("human".to_owned())); + assert!(!result.main_failed()); + + let result = proxy_program.send(USER, Action::CheckWord("horse".to_owned())); + assert!(!result.main_failed()); + + // Under probability conditions, the final game state is WIN. + let result = proxy_program.send(USER, Action::CheckGameStatus); + assert!(!result.main_failed()); + + let log = Log::builder() + .source(1) + .dest(3) + .payload(SessionStatus::GameOver(Outcome::Win)); + assert!(result.contains(&log)); + + let _result = system.spend_blocks(3); + // if target_program handle() exist `exec::wait();`, `Event::NoReplyReceived` will be received. + // Event::NoReplyReceived 1 + let _log = Log::builder() + .source(1) + .dest(3) + .payload(SessionStatus::NoReplyReceived); + // assert!(result[0].contains(&log)); + + // Event::NoReplyReceived 2 + // check that the proxy message has arrived, + // which means that the message was successfully sent to the target program + let _mailbox = system.get_mailbox(USER); + let _log = Log::builder() + .source(1) + .dest(3) + .payload(SessionStatus::NoReplyReceived); + // assert!(mailbox.contains(&log)); } diff --git a/session/wordle-state/Cargo.toml b/session/wordle-state/Cargo.toml index 7d6c56a..5b19708 100644 --- a/session/wordle-state/Cargo.toml +++ b/session/wordle-state/Cargo.toml @@ -4,10 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1",features = ["debug"] } -gmeta = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1" } +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", features = ["codegen"] } +session-game-io.path = "../io" [build-dependencies] -gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1" } -wordle-game-io.path = "../../wordle/io" - +gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1", features = ["metawasm"] } +session-game-io.path = "../io" diff --git a/session/wordle-state/build.rs b/session/wordle-state/build.rs new file mode 100644 index 0000000..d019586 --- /dev/null +++ b/session/wordle-state/build.rs @@ -0,0 +1,3 @@ +fn main() { + gear_wasm_builder::build_metawasm(); +} \ No newline at end of file diff --git a/session/wordle-state/src/lib.rs b/session/wordle-state/src/lib.rs index 7d12d9a..259c4c6 100644 --- a/session/wordle-state/src/lib.rs +++ b/session/wordle-state/src/lib.rs @@ -1,14 +1,34 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} +#[no_std] +use gmeta::metawasm; +use gstd::{prelude::*, ActorId, MessageId, Vec}; +use session_game_io::{SessionStatus, State as GameIOState}; + +#[metawasm] +pub mod metafns { + pub type State = GameIOState; + + pub fn all_player_address(state: State) -> Vec { + state.into_keys().collect() + } + + pub fn get_session_status(state: State, user_id: ActorId) -> SessionStatus { + let session = state.get(&user_id).expect("Can't find this player"); + session.session_status + } -#[cfg(test)] -mod tests { - use super::*; + pub fn get_tries_number(state: State, user_id: ActorId) -> u8 { + let session = state.get(&user_id).expect("Can't find this player"); + session.tries_number + } - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + pub fn get_sent_message_id(state: State, user_id: ActorId) -> MessageId { + let session = state.get(&user_id).expect("Can't find this player"); + session.msg_ids.0 } + + pub fn get_original_message_id(state: State, user_id: ActorId) -> MessageId { + let session = state.get(&user_id).expect("Can't find this player"); + session.msg_ids.1 + } + } diff --git a/wordle/Cargo.lock b/wordle/Cargo.lock index 3ab79e2..db8200f 100644 --- a/wordle/Cargo.lock +++ b/wordle/Cargo.lock @@ -371,7 +371,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object 0.36.0", + "object 0.36.1", "rustc-demangle", ] @@ -646,9 +646,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.101" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" +checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" [[package]] name = "cfg-expr" @@ -676,7 +676,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -692,7 +692,7 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#b273d33f9981e2bb3375ab45faeb537f7ee35224" +source = "git+https://github.com/w3f/ring-proof#96137b150288a66bc9e4df495efc64769b5d1321" dependencies = [ "ark-ec", "ark-ff", @@ -2708,9 +2708,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "loupe" @@ -2946,9 +2946,9 @@ checksum = "aeaf4ad7403de93e699c191202f017118df734d3850b01e13a3a8b2e6953d3c9" [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -3060,9 +3060,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] @@ -3153,7 +3153,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -3576,7 +3576,7 @@ dependencies = [ [[package]] name = "ring" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#b273d33f9981e2bb3375ab45faeb537f7ee35224" +source = "git+https://github.com/w3f/ring-proof#96137b150288a66bc9e4df495efc64769b5d1321" dependencies = [ "ark-ec", "ark-ff", @@ -3858,9 +3858,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -4977,9 +4977,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-encoder" -version = "0.211.1" +version = "0.212.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e7d931a1120ef357f32b74547646b6fa68ea25e377772b72874b131a9ed70d4" +checksum = "501940df4418b8929eb6d52f1aade1fdd15a5b86c92453cb696e3c906bd3fc33" dependencies = [ "leb128", ] @@ -5467,9 +5467,9 @@ dependencies = [ [[package]] name = "wast" -version = "211.0.1" +version = "212.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25506dd82d00da6b14a87436b3d52b1d264083fa79cdb72a0d1b04a8595ccaa" +checksum = "4606a05fb0aae5d11dd7d8280a640d88a63ee019360ba9be552da3d294b8d1f5" dependencies = [ "bumpalo", "leb128", @@ -5480,9 +5480,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.211.1" +version = "1.212.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb716ca6c86eecac2d82541ffc39860118fc0af9309c4f2670637bea2e1bdd7d" +checksum = "c74ca7f93f11a5d6eed8499f2a8daaad6e225cab0151bc25a091fff3b987532f" dependencies = [ "wast", ] @@ -5536,7 +5536,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -5576,7 +5576,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -5611,18 +5611,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -5639,9 +5639,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -5663,9 +5663,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -5687,15 +5687,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -5717,9 +5717,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -5741,9 +5741,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -5759,9 +5759,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -5783,9 +5783,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -5812,6 +5812,8 @@ dependencies = [ "gear-wasm-builder", "gstd", "gtest", + "parity-scale-codec", + "scale-info", "wordle-game-io", ] @@ -5837,18 +5839,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", diff --git a/wordle/Cargo.toml b/wordle/Cargo.toml index b7cf0e4..a74de8e 100644 --- a/wordle/Cargo.toml +++ b/wordle/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" [dependencies] gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1",features = ["debug"] } +parity-scale-codec = { version = "3", default-features = false } +scale-info = { version = "2", default-features = false } wordle-game-io.path = "io" [build-dependencies] @@ -14,4 +16,6 @@ wordle-game-io.path = "io" [dev-dependencies] gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.4.1", features = ["debug"] } gtest = { 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 } wordle-game-io.path = "io" diff --git a/wordle/build.rs b/wordle/build.rs index 2f97948..6836d02 100644 --- a/wordle/build.rs +++ b/wordle/build.rs @@ -1,5 +1,3 @@ -use wordle_game_io::WordleMetadata; - fn main() { - gear_wasm_builder::build_with_metadata::(); + gear_wasm_builder::build(); } diff --git a/wordle/io/src/lib.rs b/wordle/io/src/lib.rs index 9113989..f3d1286 100644 --- a/wordle/io/src/lib.rs +++ b/wordle/io/src/lib.rs @@ -1,19 +1,5 @@ #![no_std] -use gmeta::{InOut, Metadata}; -use gstd::{prelude::*, ActorId}; - -// the metadata to be used by the [IDEA](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Ftestnet.vara.network) portal. -#[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)] -pub struct WordleMetadata; - -impl Metadata for WordleMetadata { - type Init = (); - type Handle = InOut; - type Others = (); - type Reply = (); - type Signal = (); - type State = (); -} +use gstd::{prelude::*, ActorId, Decode, Encode, TypeInfo}; #[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)] #[codec(crate = gstd::codec)] diff --git a/wordle/src/lib.rs b/wordle/src/lib.rs index 3dbc738..1647c98 100644 --- a/wordle/src/lib.rs +++ b/wordle/src/lib.rs @@ -22,6 +22,7 @@ extern "C" fn init() { #[no_mangle] extern "C" fn handle() { + // exec::wait(); let action: WordleAction = msg::load().expect("Unable to decode "); let wordle = unsafe { WORDLE.as_mut().expect("The program is not initialized") }; @@ -49,7 +50,6 @@ extern "C" fn handle() { key_indices.push(i as u8); } } - WordleEvent::WordChecked { user, correct_positions: matched_indices, @@ -57,7 +57,6 @@ extern "C" fn handle() { } } }; - msg::reply(reply, 0).expect("Error in sending a reply"); } diff --git a/wordle/tests/basic.rs b/wordle/tests/basic.rs deleted file mode 100644 index e51e37a..0000000 --- a/wordle/tests/basic.rs +++ /dev/null @@ -1,59 +0,0 @@ -use gstd::ActorId; -use gtest::{Log, Program, System}; -use wordle_game_io::{WordleAction, WordleEvent}; - -const USER: u64 = 3; -const TARGET_PROGRAM_ADDRESS: u64 = 2; - -#[test] -fn success_test() { - // Create a new testing environment. - let system = System::new(); - - // Get proxy program of the root crate with provided system. - let proxy_program = Program::current(&system); - // Get target program - let target_program = Program::from_file( - &system, - "target/wasm32-unknown-unknown/release/wordle_game.opt.wasm", - ); - // The target program is initialized with an empty payload message - let result = target_program.send_bytes(USER, []); - assert!(!result.main_failed()); - - let target_program_address: ActorId = TARGET_PROGRAM_ADDRESS.into(); - // The proxy program is initialized using target_program in the payload message - let res = proxy_program.send(USER, target_program_address); - assert!(!res.main_failed()); - - // Send with the message we want to receive back - let result = proxy_program.send( - USER, - WordleAction::StartGame { - user: target_program_address, - }, - ); - assert!(!result.main_failed()); - - // check that the proxy message has arrived, - // which means that the message was successfully sent to the target program - let log = Log::builder() - .source(1) - .dest(3) - .payload(WordleEvent::GameStarted { - user: target_program_address, - }); - assert!(result.contains(&log)); - - // check that the target message has arrived at the mailbox, - // which means that a reply has been received. - let mailbox = system.get_mailbox(USER); - let log = Log::builder() - .source(1) - .dest(3) - .payload(WordleEvent::GameStarted { - user: target_program_address, - }); - - assert!(mailbox.contains(&log)); -}