diff --git a/docs/examples/archive/galactic-express.md b/docs/examples/Gaming/galactic-express.md
similarity index 59%
rename from docs/examples/archive/galactic-express.md
rename to docs/examples/Gaming/galactic-express.md
index 005f343..73ef9f2 100644
--- a/docs/examples/archive/galactic-express.md
+++ b/docs/examples/Gaming/galactic-express.md
@@ -1,6 +1,6 @@
---
sidebar_label: Galactic Express
-sidebar_position: 9
+sidebar_position: 7
---
# Galactic Express Game
@@ -12,9 +12,10 @@ Galactic Express is a game in which players guide a rocket into space, testing i
Taking their luck into their own hands, players guide a rocket into outer space, facing challenges such as dynamic weather conditions and varied flight circumstances, making each gameplay experience an exciting adventure.
Players' strategic decisions about fuel allocation and target gains significantly affect the rocket's path, making each playthrough of the game unique.
-The article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Anyone can easily create their own application and run it on the Vara Network. The source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express).
+The article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Anyone can easily create their own application and run it on the Vara Network.
+The source code, developed using the [Sails](../../build/sails/sails.mdx) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express).
-Everyone can play the game via this link - [Play Galactic Express](https://galactic-express.vara.network/). The game supports several simultaneous game sessions (lobbies). To start a new game, click the "Create game" button and share your account's public key with other players. To join your game, players need to click the "Find game" button and enter your account ID there.
+Everyone can play the game via this link - [Play Galactic Express](https://galactic-express.vara.network/). The game supports several simultaneous game sessions (lobbies). To start a new game, click the Create game button and share your account's public key with other players. To join your game, players need to click the Find game button and enter your account ID there.
## How to run the app locally
@@ -33,19 +34,22 @@ Everyone can play the game via this link - [Play Galactic Express](https://galac
The program contains the following information
-```rust title="galactic-express/src/lib.rs"
-
-struct Contract {
+```rust title="galactic-express/app/src/services/galactic_express/mod.rs"
+pub struct Storage {
games: HashMap,
player_to_game_id: HashMap,
+ dns_info: Option<(ActorId, String)>,
+ admin: ActorId,
}
```
* `games` - list of all games according to the address of the game's creator
* `player_to_game_id` - list of players and the games in which they participate
+* `dns_info` - optional field that stores the [dDNS](../Infra/dein.md) address and the program name
+* `admin` - game admin
Where the `Game` is as follows:
-```rust title="galactic-express/src/lib.rs"
+```rust title="galactic-express/app/src/services/galactic_express/mod.rs"
pub struct Game {
admin: ActorId,
admin_name: String,
@@ -67,8 +71,8 @@ pub struct Game {
There are two possible states: one during the registration stage and the other when the final results are already available
-```rust title="galactic-express/src/lib.rs"
-enum Stage {
+```rust title="galactic-express/app/src/services/galactic_express/utils.rs"
+pub enum Stage {
Registration(HashMap),
Results(Results),
}
@@ -76,7 +80,7 @@ enum Stage {
The `Participant` structure stores information about its address, name, fuel and payload amount
-```rust title="galactic-express/io/src/lib.rs"
+```rust title="galactic-express/app/src/services/galactic_express/utils.rs"
pub struct Participant {
pub id: ActorId,
pub name: String,
@@ -87,7 +91,7 @@ pub struct Participant {
The `Results` record all possible events during the players' turns, the number of scores they have earned and information about the participants
-```rust title="galactic-express/io/src/lib.rs"
+```rust title="galactic-express/app/src/services/galactic_express/utils.rs"
pub struct Results {
pub turns: Vec>,
pub rankings: Vec<(ActorId, u128)>,
@@ -97,7 +101,7 @@ pub struct Results {
In flight, a rocket can either use up some amount of fuel or be destroyed by various space conditions, which are described in `HaltReason`
-```rust title="galactic-express/io/src/lib.rs"
+```rust title="galactic-express/app/src/services/galactic_express/utils.rs"
pub enum Turn {
Alive { fuel_left: u8, payload_amount: u8 },
Destroyed(HaltReason),
@@ -115,52 +119,54 @@ pub enum HaltReason {
### Initialization
-This program has no input data for initialization, and anyone who uploads the program to the network automatically becomes an admin
+To initialize the game program, a DNS address and name can be specified.
-```rust title="galactic-express/src/lib.rs"
-fn process_init() -> Result<(), Error> {
- unsafe {
- STATE = Some((
- Contract {
+```rust title="galactic-express/app/src/services/galactic_express/mod.rs"
+ pub async fn init(dns_id_and_name: Option<(ActorId, String)>) -> Self {
+ unsafe {
+ STORAGE = Some(Storage {
+ dns_info: dns_id_and_name.clone(),
+ admin: msg::source(),
..Default::default()
- },
- TransactionManager::new(),
- ));
- }
+ });
+ }
- Ok(())
-}
+ if let Some((id, name)) = dns_id_and_name {
+ let request = [
+ "Dns".encode(),
+ "AddNewProgram".to_string().encode(),
+ (name, exec::program_id()).encode(),
+ ]
+ .concat();
+
+ msg::send_bytes_with_gas_for_reply(id, request, 5_000_000_000, 0, 0)
+ .expect("Error in sending message")
+ .await
+ .expect("Error in `AddNewProgram`");
+ }
+ Self(())
+ }
```
### Action
-```rust title="galactic-express/io/src/lib.rs"
-pub enum Action {
- CreateNewSession {
- name: String,
- },
- Register {
- creator: ActorId,
- participant: Participant,
- },
- CancelRegistration,
- DeletePlayer {
- player_id: ActorId,
- },
- CancelGame,
- LeaveGame,
- StartGame {
- fuel_amount: u8,
- payload_amount: u8,
- },
-}
+```rust title="galactic-express/app/src/services/galactic_express/mod.rs"
+ pub fn create_new_session(&mut self, name: String);
+ pub fn cancel_game(&mut self);
+ pub fn leave_game(&mut self);
+ pub fn register(&mut self, creator: ActorId, participant: Participant);
+ pub fn cancel_register(&mut self);
+ pub fn delete_player(&mut self, player_id: ActorId);
+ pub fn start_game(&mut self, fuel_amount: u8, payload_amount: u8);
+ pub fn change_admin(&mut self, new_admin: ActorId);
+ pub async fn kill(&mut self, inheritor: ActorId);
```
### Event
-```rust title="galactic-express/io/src/lib.rs"
+```rust title="galactic-express/app/src/services/galactic_express/mod.rs"
pub enum Event {
- AdminChanged(ActorId, ActorId),
+ GameFinished(Results),
NewSessionCreated {
altitude: u16,
weather: Weather,
@@ -173,123 +179,91 @@ pub enum Event {
player_id: ActorId,
},
GameCanceled,
- GameFinished(Results),
GameLeft,
+ AdminChanged {
+ new_admin: ActorId,
+ },
+ Killed {
+ inheritor: ActorId,
+ },
}
+
```
### Logic
-A new game session must be set up using `Action::CreateNewSession` to start the game.
+A new game session must be set up using `create_new_session` to start the game.
Upon the inception of a new session, random values, encompassing aspects like weather conditions, altitude settings, fuel prices, and potential rewards, are dynamically generated.
-```rust title="galactic-express/src/lib.rs"
-fn create_new_session(&mut self, name: String) -> Result {
- let msg_src = msg::source();
+```rust title="galactic-express/app/src/services/galactic_express/funcs.rs"
+pub fn register(
+ storage: &mut Storage,
+ creator: ActorId,
+ participant: Participant,
+) -> Result {
+ let msg_source = msg::source();
let msg_value = msg::value();
-
- if self.player_to_game_id.contains_key(&msg_src) {
- return Err(Error::SeveralRegistrations);
- }
-
- let game = self.games.entry(msg_src).or_insert_with(|| Game {
- admin: msg_src,
- admin_name: name,
- bid: msg_value,
- ..Default::default()
- });
-
- let stage = &mut game.stage;
-
- match stage {
- Stage::Registration(participants) => {
- participants.clear();
- }
- Stage::Results { .. } => *stage = Stage::Registration(HashMap::new()),
+ let reply = register_for_session(storage, creator, participant, msg_source, msg_value);
+ if reply.is_err() {
+ send_value(msg_source, msg_value);
}
-
- let mut random = Random::new()?;
-
- game.weather = match random.next() % (Weather::Tornado as u8 + 1) {
- 0 => Weather::Clear,
- 1 => Weather::Cloudy,
- 2 => Weather::Rainy,
- 3 => Weather::Stormy,
- 4 => Weather::Thunder,
- 5 => Weather::Tornado,
- _ => unreachable!(),
- };
- game.altitude = random.generate(TURN_ALTITUDE.0, TURN_ALTITUDE.1) * TURNS as u16;
- game.reward = random.generate(REWARD.0, REWARD.1);
- self.player_to_game_id.insert(msg_src, msg_src);
-
- Ok(Event::NewSessionCreated {
- altitude: game.altitude,
- weather: game.weather,
- reward: game.reward,
- bid: msg_value,
- })
+ reply
}
-```
-
-After successfully creating a new session, players can begin registration: `Action::Register(Participant)`
-```rust title="galactic-express/src/lib.rs"
-fn register(
- &mut self,
+fn register_for_session(
+ storage: &mut Storage,
creator: ActorId,
participant: Participant,
msg_source: ActorId,
msg_value: u128,
-) -> Result {
- if self.player_to_game_id.contains_key(&msg_source) {
- return Err(Error::SeveralRegistrations);
+) -> Result {
+ if storage.player_to_game_id.contains_key(&msg_source) {
+ return Err(GameError::SeveralRegistrations);
}
- if let Some(game) = self.games.get_mut(&creator) {
+ if let Some(game) = storage.games.get_mut(&creator) {
if msg_value != game.bid {
- return Err(Error::WrongBid);
+ return Err(GameError::WrongBid);
}
if let Stage::Results(_) = game.stage {
- return Err(Error::SessionEnded);
+ return Err(GameError::SessionEnded);
}
let participants = game.stage.mut_participants()?;
if participants.contains_key(&msg_source) {
- return Err(Error::AlreadyRegistered);
+ return Err(GameError::AlreadyRegistered);
}
if participants.len() >= MAX_PARTICIPANTS - 1 {
- return Err(Error::SessionFull);
+ return Err(GameError::SessionFull);
}
participant.check()?;
participants.insert(msg_source, participant.clone());
- self.player_to_game_id.insert(msg_source, creator);
+ storage.player_to_game_id.insert(msg_source, creator);
Ok(Event::Registered(msg_source, participant))
} else {
- Err(Error::NoSuchGame)
+ Err(GameError::NoSuchGame)
}
}
```
-This function checks the game stage, the number of registered players and the participant's input data.
+This function `register_for_session` checks the game stage, the number of registered players and the participant's input data.
Input values of fuel and payload cannot be exceeded by predetermined values
-
-```rust title="galactic-express/io/src/lib.rs"
+```rust title="galactic-express/app/src/services/galactic_express/utils.rs"
// maximum fuel value that can be entered by the user
pub const MAX_FUEL: u8 = 100;
// maximum payload value that can be entered by the user
pub const MAX_PAYLOAD: u8 = 100;
// ...
impl Participant {
- pub fn check(&self) -> Result<(), Error> {
+ pub fn check(&self) -> Result<(), GameError> {
if self.fuel_amount > MAX_FUEL || self.payload_amount > MAX_PAYLOAD {
- Err(Error::FuelOrPayloadOverload)
+ Err(GameError::FuelOrPayloadOverload)
} else {
Ok(())
}
@@ -297,17 +271,23 @@ impl Participant {
}
```
-After players have successfully registered, the admin can initiate the game using the `Action::StartGame(Participant)` action. This action involves several checks on the admin, the number of participants, and their data.
-
+After players have successfully registered, the admin can initiate the game using the `start_game` function. This function involves several checks on the admin, the number of participants, and their data.
-```rust title="galactic-express/src/lib.rs"
-async fn start_game(&mut self, fuel_amount: u8, payload_amount: u8) -> Result {
+```rust title="galactic-express/app/src/services/galactic_express/funcs.rs"
+pub fn start_game(
+ storage: &mut Storage,
+ fuel_amount: u8,
+ payload_amount: u8,
+) -> Result {
let msg_source = msg::source();
- let game = self.games.get_mut(&msg_source).ok_or(Error::NoSuchGame)?;
+ let game = storage
+ .games
+ .get_mut(&msg_source)
+ .ok_or(GameError::NoSuchGame)?;
if fuel_amount > MAX_FUEL || payload_amount > MAX_PAYLOAD {
- return Err(Error::FuelOrPayloadOverload);
+ return Err(GameError::FuelOrPayloadOverload);
}
let participant = Participant {
id: msg_source,
@@ -319,7 +299,7 @@ async fn start_game(&mut self, fuel_amount: u8, payload_amount: u8) -> Result = turns
.iter()
.map(|(actor, turns)| {
@@ -402,66 +381,57 @@ let mut scores: Vec<(ActorId, u128)> = turns
.collect();
```
-## Program metadata and state
-Metadata interface description:
+## Query
+
+```rust title="galactic-express/app/src/services/galactic_express/mod.rs"
+ pub fn get_game(&self, player_id: ActorId) -> Option {
+ let storage = self.get();
+ storage
+ .player_to_game_id
+ .get(&player_id)
+ .and_then(|creator_id| storage.games.get(creator_id))
+ .map(|game| {
+ let stage = match &game.stage {
+ Stage::Registration(participants_data) => {
+ StageState::Registration(participants_data.clone().into_iter().collect())
+ }
+ Stage::Results(results) => StageState::Results(results.clone()),
+ };
+
+ GameState {
+ admin: game.admin,
+ admin_name: game.admin_name.clone(),
+ altitude: game.altitude,
+ weather: game.weather,
+ reward: game.reward,
+ stage,
+ bid: game.bid,
+ }
+ })
+ }
+ pub fn all(&self) -> State {
+ self.get().into()
+ }
+ pub fn admin(&self) -> &'static ActorId {
+ &self.get().admin
+ }
+ pub fn dns_info(&self) -> &'static Option<(ActorId, String)> {
+ &self.get().dns_info
+ }
+```
-```rust title="galactic-express/io/src/lib.rs"
-pub struct ContractMetadata;
+- `get_game(&self, player_id: ActorId)`: Retrieves the game state for a specific player based on their `player_id`
-impl Metadata for ContractMetadata {
- type Init = Out>;
- type Handle = InOut>;
- type Reply = ();
- type Others = ();
- type Signal = ();
- type State = InOut;
+- `all(&self)`: Provides the overall state of the storage
-```
+- `admin(&self)`: Returns the admin’s `ActorId`
-To display the program state information, the `state()` function is used:
-
-```rust title="galactic-express/src/lib.rs"
-#[no_mangle]
-extern fn state() {
- let (state, _tx_manager) = unsafe { STATE.take().expect("Unexpected error in taking state") };
- let query: StateQuery = msg::load().expect("Unable to load the state query");
- let reply = match query {
- StateQuery::All => StateReply::All(state.into()),
- StateQuery::GetGame { player_id } => {
- let game_state = state
- .player_to_game_id
- .get(&player_id)
- .and_then(|creator_id| state.games.get(creator_id))
- .map(|game| {
- let stage = match &game.stage {
- Stage::Registration(participants_data) => StageState::Registration(
- participants_data.clone().into_iter().collect(),
- ),
- Stage::Results(results) => StageState::Results(results.clone()),
- };
-
- GameState {
- admin: game.admin,
- admin_name: game.admin_name.clone(),
- altitude: game.altitude,
- weather: game.weather,
- reward: game.reward,
- stage,
- bid: game.bid,
- }
- });
-
- StateReply::Game(game_state)
- }
- };
- msg::reply(reply, 0).expect("Unable to share the state");
-}
-```
+- `dns_info(&self)`: Returns the optional dDNS information, including the dDNS address and program name, if available.
## Source code
The source code of this example of Galactic-Express Game program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/galactic-express](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express).
-See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/galactic-express/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express/tests).
+See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/galactic-express/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express/app/tests).
For more details about testing programs written on Vara, refer to the [Program Testing](/docs/build/testing) article.