4
\$\begingroup\$

I watched a YouTube video (https://www.youtube.com/watch?v=z-0-bbc80JM) talking about the power of enums in rust for data modelling. In the video, there's an example of a state machine of a simple Mario game using enum and match to define possible states and transitions.

I implemented the same code from the video and tried to add new things to it, and I got stuck because I don't know a good way to add new attributes to the transition. For example, I wanted to make some items on the game be able to revive the player, and items on the game are the transition between states, so for example if you pick up an flower it should be able to change your state to an FireMario, and an feather to the CapeMario, but only the Feather can revive the player.

Here is a code example:

#[derive(Debug)]
enum TransitionProperty {
 Revive,
 None,
}
#[derive(Debug)]
enum Transition {
 Mushroom(TransitionProperty),
 Flower(TransitionProperty),
 Feather(TransitionProperty),
 Damage(TransitionProperty),
}
impl Transition {
 fn new(transition: Self) -> Self {
 match transition {
 Transition::Mushroom(_) => Transition::Mushroom(TransitionProperty::None),
 Transition::Flower(_) => Transition::Flower(TransitionProperty::None),
 Transition::Damage(_) => Transition::Damage(TransitionProperty::None),
 Transition::Feather(_) => Transition::Feather(TransitionProperty::Revive),
 }
 }
}
#[derive(Debug)]
enum State {
 Mario,
 SuperMario,
 FireMario,
 CapeMario,
 Dead,
}
#[derive(Debug)]
struct Player {
 state: State,
 // health: usize,
}
impl Player {
 fn new() -> Self {
 let pl = Self {
 state: (State::Mario),
 // health: 1,
 };
 #[cfg(debug_assertions)]
 println!("Creating player, Starting fields: {:?}", pl);
 pl
 }
 fn consume(&mut self, item: Transition) {
 #[cfg(debug_assertions)]
 print!("Consuming {:?}, ", &item);
 match (&self.state, &item) {
 (State::Dead, _) => {}
 (State::Mario, Transition::Damage) => self.state = State::Dead,
 (State::Mario, Transition::Mushroom) => self.state = State::SuperMario,
 (_, Transition::Flower) => self.state = State::FireMario,
 (_, Transition::Feather) => self.state = State::CapeMario,
 (_, Transition::Mushroom) => {
 // if self.health < 3 {
 // self.health += 1;
 // #[cfg(debug_assertions)]
 // print!("Won a extra life, health = {}, ", self.health);
 // }
 }
 (State::SuperMario, Transition::Damage) => self.state = State::Mario,
 (_, Transition::Damage) => self.state = State::SuperMario,
 };
 #[cfg(debug_assertions)]
 println!("Current state: {:?}", self.state);
 }
}
fn main() {
 let mut player1 = Player::new();
 player1.consume(Transition::Mushroom);
 player1.consume(Transition::Feather);
 player1.consume(Transition::Flower);
 player1.consume(Transition::Mushroom);
 player1.consume(Transition::Mushroom);
 player1.consume(Transition::Mushroom);
 player1.consume(Transition::Mushroom);
 player1.consume(Transition::Damage);
 player1.consume(Transition::Damage);
 player1.consume(Transition::Flower);
 player1.consume(Transition::Damage);
 player1.consume(Transition::Damage);
 player1.consume(Transition::Damage);
 player1.consume(Transition::Damage);
 #[cfg(debug_assertions)]
 println!("{:?}", player1);
 println!("Finish.");
}

To make the code compile, just remove everything related to the TransitionProperty. How could I approach the problem better?

toolic
15.9k6 gold badges29 silver badges217 bronze badges
asked May 15, 2024 at 4:01
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

You would potentially want to add a TransitionProperty to Transition::Flower only if you had multiple kinds of flowers: ones that can revive and ones that can't. But that doesn't look like that's what you intend.

If you just want to have transitions be classified by certain properties that multiple transitions have, that is just behavior that can be expressed by a method.

enum Transition {
 Mushroom,
 Flower,
 Feather,
 Damage,
}
impl Transition {
 fn property(&self) -> TransitionProperty {
 match self {
 Transition::Mushroom => TransitionProperty::None,
 Transition::Flower => TransitionProperty::None,
 Transition::Damage => TransitionProperty::None,
 Transition::Feather => TransitionProperty::Revive,
 }
 }
}

And then to handle transitions using that property in your state transition mapping, you can use a match guard (the if ... part of the arm):

fn consume(&mut self, item: Transition) {
 match (&self.state, &item) {
 (State::Dead, item) if item.property() == TransitionProperty::Revive => {
 // revive mario
 self.state = State::Mario
 }
 (State::Dead, _) => {
 // leave dead
 }
 ...
 };
}

Make sure you #[derive(PartialEq, Eq)] on your TransitionProperty for the above condition to work.

answered May 15, 2024 at 4:52
\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.