#  False Is Not an Answer.

Canonical: <https://journeytocode.io/rust-enums-and-match-click-upgrades/>

Our player earns gold now. After eight stages, they have ninety-six of it and nothing to spend it on.

That is not a reward. That is a high score.

So today we are going to sell them something: more damage per click, at a price that goes up every time they buy it.

The shopping is the easy part.

The interesting part is what happens when we try to tell the rest of the program whether the purchase worked.

Because the obvious answer, `true` or `false`, turns out to be not quite an answer at all.

```text
Bought click upgrade 1! Click damage: 7  Gold left: 2
```

Along the way: four quick knowledge checks, two live examples you can edit and run in the page, and a coding challenge at the end.

---

## Our starting point

If you are only reading along, [skip past this code to "The free upgrade nobody earned."](https://journeytocode.io/rust-enums-and-match-click-upgrades/#the-free-upgrade-nobody-earned)

If you are building along, you need three structs, three `impl` blocks, nine small functions, and thirteen tests:

<details>
<summary><code>src/main.rs</code> · 268-line starting point</summary>
<pre><code class="language-rust">struct Player {
    bonus_damage: u32,
    gold: u32,
    xp: u32,
    level: u32,
}

struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
    xp_reward: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

impl Player {
    fn new() -> Player {
        Player {
            bonus_damage: 2,
            gold: 0,
            xp: 0,
            level: 1,
        }
    }

    fn add_gold(&mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    fn add_xp(&mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp >= xp_to_next_level(self.level) {
            self.xp -= xp_to_next_level(self.level);
            self.level = self.level.saturating_add(1);
        }
    }

    fn base_damage(&self) -> u32 {
        base_damage_for_level(self.level)
    }
}

impl Enemy {
    fn for_stage(stage: u32) -> Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -> Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&mut self) {
        attack(&self.player, &mut self.enemy);

        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }
}

fn click_damage(base: u32, bonus: u32) -> u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(health: u32, damage: u32) -> u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -> bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -> u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn gold_reward_for_stage(stage: u32) -> u32 {
    stage.saturating_mul(2).saturating_add(3)
}

fn xp_reward_for_stage(stage: u32) -> u32 {
    stage.saturating_mul(2).saturating_add(4)
}

fn xp_to_next_level(level: u32) -> u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

fn base_damage_for_level(level: u32) -> u32 {
    level.saturating_add(4)
}

fn attack(player: &Player, enemy: &mut Enemy) {
    let damage = click_damage(player.base_damage(), player.bonus_damage);

    enemy.health = health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage <= 8 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage > stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                xp_to_next_level(game.player.level)
            );

            if game.player.level > level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    click_damage(game.player.base_damage(), game.player.bonus_damage)
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&player, &mut enemy);

        assert_eq!(enemy.health, 3);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 13);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }
}
</code></pre>
</details>

`Player` stores what the player has earned: gold, XP, and a level. Defeating an enemy pays gold and XP, and enough XP levels the player up.

Click damage comes from two places. `base_damage()` works it out from the player's level. `bonus_damage` is just a number stored in the struct.

Run `cargo test` and all thirteen should pass.

---

## The free upgrade nobody earned

Look at that `Player` again.

```rust
    bonus_damage: u32,
```

Every new player starts with 2 of it. For free. Since the first time we gave the player a second damage number.

`base_damage` is already a method, not a field. It is worked out from the player's level, because it is a consequence of that level, not a fact to store.

`bonus_damage` has the same problem, with a different cause.

Bonus damage should be a consequence of how many upgrades the player has bought.

So the player does not need to store the bonus. The player needs to store the shopping.

Two rules, under `base_damage_for_level()`:

```rust
fn click_upgrade_bonus(upgrades: u32) -> u32 {
    upgrades.saturating_mul(2)
}

fn click_upgrade_cost(upgrades: u32) -> u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}
```

Each upgrade adds 2 damage:

```text
0 upgrades → 0 bonus
1 upgrade  → 2 bonus
2 upgrades → 4 bonus
```

And each one costs more than the last:

```text
first upgrade  → 10 gold
second upgrade → 20 gold
third upgrade  → 30 gold
```

Both rules take the number of upgrades already bought. Neither one knows a `Player` exists.

If you build now, Rust warns that both are never used. The next few steps use them.

Want to feel the price curve before you commit to it? Edit the numbers and press Run.

**Live example · tune the shop**

```rust
fn click_upgrade_bonus(upgrades: u32) -> u32 {
    upgrades.saturating_mul(2)
}

fn click_upgrade_cost(upgrades: u32) -> u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}

fn main() {
    for upgrades in 0..5 {
        println!(
            "{upgrades} bought: +{} damage, next one costs {} gold",
            click_upgrade_bonus(upgrades),
            click_upgrade_cost(upgrades)
        );
    }
}
```

[Run and edit this example on the canonical article](https://journeytocode.io/rust-enums-and-match-click-upgrades/).

Try `saturating_mul(3)` for a stronger upgrade, or `saturating_mul(25)` for a pricier one. Keep the original numbers in your project, though. The run output later in this article depends on them.

Now replace the field with a count of purchases:

```rust
struct Player {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}
```

Rust immediately complains about every place that still mentions `bonus_damage`. The next few steps clear those errors one at a time.

A new player has bought nothing:

```rust
            click_upgrade_level: 0,
```

And just like base damage, bonus damage becomes a method that asks the rule. Put it under `base_damage()`:

```rust
    fn bonus_damage(&self) -> u32 {
        click_upgrade_bonus(self.click_upgrade_level)
    }
```

`attack()` gets its second pair of parentheses:

```rust
    let damage = click_damage(player.base_damage(), player.bonus_damage());
```

One more place still reads the old field: the "Level up!" line in `main()`. Rust points straight at it (the line is wrapped here to fit):

```text
error[E0615]: attempted to take value of method
`bonus_damage` on type `Player`
```

It even suggests the fix: use parentheses to call the method.

```rust
                    click_damage(game.player.base_damage(), game.player.bonus_damage())
```

Now it builds again.

Notice what `Player` is made of now.

```text
Player
├── gold
├── xp
├── level
└── click_upgrade_level
```

Not a single damage number.

Every field is a record of **progress**: what the player has earned, and what they have done with it.

The damage numbers are worked out from that progress, by rules, on demand.

That is going to matter a great deal in a few articles, when we save the game.

We will only have to save what the player earned. Everything else can be recalculated.

**Question:** Why does `Player` now store `click_upgrade_level` instead of `bonus_damage`?

1. Rust does not allow a struct to store a damage number
2. The bonus is a consequence of what the player bought, so a rule can work it out
3. Calling a method is faster than reading a field
4. So the player can never buy more than one upgrade

**Answer:** 2. The bonus is a consequence of what the player bought, so a rule can work it out

> **Explanation:** Store the progress, compute the rest. The number of purchases is the fact, and `click_upgrade_bonus()` turns it into damage whenever we ask.

### The first fight changes

One honest side effect.

A new player used to hit for 7: 5 base plus 2 free bonus.

Now they hit for 5, because the bonus has to be bought.

The stage 1 slime still has 20 health, so it takes four clicks instead of three.

Two of our tests notice.

`clicking_damages_the_enemy` expected 13 health after one click. It is 15 now.

`attack_reduces_enemy_health` expected 3 health left from 10. It is 5.

Update both numbers and run `cargo test`.

Those tests were not broken. The game changed, and the tests were telling the truth about it.

---

## Buying the upgrade

Here is the first version of the purchase. Add it to `impl Player`, under `bonus_damage()`:

```rust
    fn buy_click_upgrade(&mut self) -> bool {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold < cost {
            return false;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        true
    }
```

Work out the price.

If the player cannot afford it, stop, and say so.

Otherwise, take the money, add the upgrade, and say so.

Two things to notice.

`return false;` leaves the method early.

Everything after it only runs if the player could afford the upgrade.

That lets the rest of the method read without an `else` wrapped around it.

The `true` on the last line needs no `return`. It is the same rule every function so far has used: the last expression, with no semicolon, is the value the function gives back. `return` is only for leaving from somewhere in the middle.

And `self.gold -= cost;` is a plain minus sign, like the one in `add_xp()`.

The check right above it guarantees `self.gold` is at least `cost`, so the subtraction cannot go below zero.

Then `Game` passes the request along, inside `impl Game`:

```rust
    fn buy_click_upgrade(&mut self) -> bool {
        self.player.buy_click_upgrade()
    }
```

The game does not know the price of an upgrade.

It does not know what happens when you buy one.

It knows the player handles that.

Nothing calls either method yet, so Rust warns that they are never used, and `click_upgrade_cost()` along with them. `main()` will fix that shortly.

---

## `false` is not an answer

This works.

Now imagine using it:

```rust
if game.buy_click_upgrade() {
    println!("Bought it!");
} else {
    println!("???");
}
```

What goes in the `else`?

`false` means the purchase did not happen.

It does not say *why*.

Today there is only one reason: not enough gold.

But look at this from the other side.

The code calling `buy_click_upgrade()` has to *remember* that `false` means "not enough gold."

Nothing in the code says so.

Add a second reason to fail someday, say a maximum upgrade level, and `false` starts meaning two different things.

Every place that checked it is now quietly wrong, and Rust will not say a word.

We want an answer that says what happened in plain words, and that Rust can check for us.

**Question:** `buy_click_upgrade()` returns `false`. What does the calling code actually know?

1. That the player did not have enough gold
2. Only that the purchase did not happen
3. That the method crashed

**Answer:** 2. Only that the purchase did not happen

> **Explanation:** `false` says no, and nothing else. The reason lives in the programmer's memory, not in the code, and a second way to fail would hide behind the same `false`.

---

## Give the outcome a name

Rust has a type for "one of these specific things":

```rust
#[derive(Debug, PartialEq)]
enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}
```

Put it with the structs, after `Game`. It is a type, like they are. Until something uses it, Rust adds one more never-used warning to the pile.

`enum` is short for *enumeration*: a type whose values are listed out, one by one.

A `PurchaseOutcome` is either `Purchased` or `NotEnoughGold`.

It cannot be anything else.

It cannot be both.

The two options are called **variants**, and you name one the same way you reach into a type for a function:

```rust
PurchaseOutcome::NotEnoughGold
```

You can think about `bool` the same way for this purpose: it also stands for one of exactly two possibilities, `true` or `false`.

The difference is that our enum lets us give those possibilities names that belong to our game.

### The line above the enum

```rust
#[derive(Debug, PartialEq)]
```

`PartialEq` is what lets `==` compare two outcomes. Without it, Rust has no idea whether `Purchased == Purchased`.

`Debug` is what lets Rust print an outcome for a programmer to read, so a failing test can show `NotEnoughGold` in its message.

Numbers like `u32` come with both abilities built in. Our brand-new enum starts with neither.

Rust calls abilities like these **traits**. That is all you need about traits today: a trait is a named ability a type can have, like "can be compared with `==`."

`#[derive(...)]` is the one-line way to ask Rust for the standard version of a trait, instead of writing it by hand.

Our tests will need both.

### Use it

Now the purchase can say what happened:

```rust
    fn buy_click_upgrade(&mut self) -> PurchaseOutcome {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold < cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
```

Same shape. Same early return.

Only the answers changed, and now they read like the game talking.

`Game::buy_click_upgrade()` changes its return type to match:

```rust
    fn buy_click_upgrade(&mut self) -> PurchaseOutcome {
        self.player.buy_click_upgrade()
    }
```

---

## `match`: one branch per answer

Rust's `match` expression gives us a way to handle each possible variant explicitly.

Here is how `main()` will react to a purchase:

```rust
match game.buy_click_upgrade() {
    PurchaseOutcome::Purchased => println!("Bought it!"),
    PurchaseOutcome::NotEnoughGold => {}
}
```

`match` looks at the value and runs the branch that fits.

Each line is called an **arm**: a pattern, an arrow, and what to do.

That arrow is `=>`, a fat arrow. It is not the `->` from function signatures, and your fingers will try to type that one at least once.

Arms end with a comma, like items in a list. An arm whose body is a block in braces can leave the comma off.

The `{}` on the second branch means: do nothing.

That is allowed. What is not allowed is leaving a variant out.

Delete the `NotEnoughGold` line and Rust refuses to compile:

```text
error[E0004]: non-exhaustive patterns:
`PurchaseOutcome::NotEnoughGold` not covered
```

This is the payoff.

With our `bool` version, the caller had to remember what `false` meant.

With an enum and `match`, the possible outcomes are named, and Rust can make us handle every one.

If we add a third variant next month, every `match` that names the variants will stop compiling until it says what to do about it.

Rust is keeping a list for us.

The full error message also offers to fill the gap for you, with a *wildcard pattern* or a `todo!()` placeholder that crashes if it ever runs. A wildcard (`_ =>`, meaning "anything else") would compile, but it switches this check off for every variant you add later. Naming each variant is what keeps the list.

Try it. Run this, then delete one arm and run it again. Then put the arm back, add a third variant to the enum, and run it without touching the `match`.

**Live example · break the match on purpose**

```rust
enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}

fn try_to_buy(gold: u32, cost: u32) -> PurchaseOutcome {
    if gold < cost {
        return PurchaseOutcome::NotEnoughGold;
    }

    PurchaseOutcome::Purchased
}

fn main() {
    for gold in [5, 10, 15] {
        match try_to_buy(gold, 10) {
            PurchaseOutcome::Purchased => println!("{gold} gold: bought it!"),
            PurchaseOutcome::NotEnoughGold => println!("{gold} gold: not enough."),
        }
    }
}
```

[Run and edit this example on the canonical article](https://journeytocode.io/rust-enums-and-match-click-upgrades/).

**Question:** You add a `MaxLevelReached` variant to `PurchaseOutcome`. What does Rust do the next time you build?

1. Nothing, until a purchase actually returns `MaxLevelReached`
2. It refuses to compile the enum itself
3. Every `match` that names the variants, but not the new one, stops compiling
4. Every `match` quietly skips the new variant

**Answer:** 3. Every `match` that names the variants, but not the new one, stops compiling

> **Explanation:** The check happens when the program is built, not when it runs. The `match` in `main()` gets an `E0004` error until it handles the new variant.

**Question:** Rust reports `E0004` for a missing arm, and you silence it by adding `_ => {}`. What have you given up?

1. The existing arms stop running
2. Rust will no longer tell you when a variant added later needs handling
3. The `match` no longer compiles

**Answer:** 2. Rust will no longer tell you when a variant added later needs handling

> **Explanation:** `_` means "anything else", so a future variant lands in it silently. Naming every variant is what keeps Rust's list.

---

## `main()` goes shopping

Add the purchase to the loop, after the stage-cleared block:

```rust
        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased => println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                click_damage(game.player.base_damage(), game.player.bonus_damage()),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold => {}
        }
```

Every turn, `main()` tries to buy an upgrade, and only says something when it works.

Worth being clear about what that means: in this terminal version, the game buys an upgrade the moment the player can afford one. Nobody is deciding anything.

Later, when this game has a button, the player will choose when to spend. The purchasing rule does not change when that happens. Only who calls it.

Run it:

```bash
cargo run
```

```text
Stage 1 cleared!  Gold: 5  XP: 6/20
  Next up: Training Slime with 30 health.
Stage 2 cleared!  Gold: 12  XP: 14/20
  Next up: Training Slime with 40 health.
Bought click upgrade 1! Click damage: 7  Gold left: 2
Stage 3 cleared!  Gold: 11  XP: 4/35
Level up! Now level 2. Click damage: 8
  Next up: Training Slime with 50 health.
Stage 4 cleared!  Gold: 22  XP: 16/35
  Next up: Training Slime with 60 health.
Bought click upgrade 2! Click damage: 10  Gold left: 2
Stage 5 cleared!  Gold: 15  XP: 30/35
  Next up: Training Slime with 70 health.
Stage 6 cleared!  Gold: 30  XP: 11/50
Level up! Now level 3. Click damage: 11
  Next up: Training Slime with 80 health.
Bought click upgrade 3! Click damage: 13  Gold left: 0
Stage 7 cleared!  Gold: 17  XP: 29/50
  Next up: Training Slime with 90 health.
Stage 8 cleared!  Gold: 36  XP: 49/50
  Next up: Training Slime with 100 health.
```

Now gold goes somewhere.

It climbs, and then it drops to almost nothing, because something got bought.

Click damage goes 5 → 7 → 8 → 10 → 11 → 13, pushed up from two directions at once: levels the player earns, and upgrades the player buys.

Compare the last line to where we started this article. Ninety-six gold and nothing to do with it, versus thirty-six gold and three upgrades already paid for.

---

## Testing the purchase

The pricing rule gets a test for its numbers. Put the new tests in `mod tests`, after `levels_make_the_player_hit_harder`:

```rust
    #[test]
    fn upgrades_cost_more_each_time() {
        assert_eq!(click_upgrade_cost(0), 10);
        assert_eq!(click_upgrade_cost(1), 20);
    }
```

Then the two things that can happen.

A broke player cannot buy anything, and a failed purchase changes nothing:

```rust
    #[test]
    fn buying_an_upgrade_needs_gold() {
        let mut player = Player::new();

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::NotEnoughGold);
        assert_eq!(player.click_upgrade_level, 0);
        assert_eq!(player.bonus_damage(), 0);
    }

    #[test]
    fn buying_an_upgrade_spends_gold() {
        let mut player = Player::new();
        player.gold = 12;

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::Purchased);
        assert_eq!(player.gold, 2);
        assert_eq!(player.click_upgrade_level, 1);
        assert_eq!(player.bonus_damage(), 2);
    }
```

Those `assert_eq!` lines comparing outcomes are exactly why we asked for `PartialEq` and `Debug`.

`assert_eq!` needs both. `PartialEq` does the comparison. `Debug` prints the two sides if they do not match. Leave either one out of the `derive` line and these tests will not even compile.

`cargo run` would still build without them, because `match` needs neither. It is the tests that ask.

**Question:** You delete `Debug` from `#[derive(Debug, PartialEq)]`. What happens to the test that runs `assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::Purchased)`?

1. It compiles, but a failing test prints an empty message
2. The test code no longer compiles
3. Nothing changes
4. It panics when the test runs

**Answer:** 2. The test code no longer compiles

> **Explanation:** `assert_eq!` has to be able to print both sides in case they differ, so it needs `Debug` before the test can even be built.

Run them:

```bash
cargo test
```

All sixteen should pass.

---

## The complete program

If you are only reading along, [skip ahead to the coding challenge](https://journeytocode.io/rust-enums-and-match-click-upgrades/#your-turn-cap-the-upgrades).

If you have been following along in your editor, here is everything in one place.

At this point, `src/main.rs` should look like this:

<details>
<summary><code>src/main.rs</code> · complete 339-line program</summary>
<pre><code class="language-rust">struct Player {
    gold: u32,
    xp: u32,
    level: u32,
    click_upgrade_level: u32,
}

struct Enemy {
    name: String,
    health: u32,
    gold_reward: u32,
    xp_reward: u32,
}

struct Game {
    stage: u32,
    player: Player,
    enemy: Enemy,
}

#[derive(Debug, PartialEq)]
enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
}

impl Player {
    fn new() -> Player {
        Player {
            gold: 0,
            xp: 0,
            level: 1,
            click_upgrade_level: 0,
        }
    }

    fn add_gold(&mut self, amount: u32) {
        self.gold = self.gold.saturating_add(amount);
    }

    fn add_xp(&mut self, amount: u32) {
        self.xp = self.xp.saturating_add(amount);

        while self.xp >= xp_to_next_level(self.level) {
            self.xp -= xp_to_next_level(self.level);
            self.level = self.level.saturating_add(1);
        }
    }

    fn base_damage(&self) -> u32 {
        base_damage_for_level(self.level)
    }

    fn bonus_damage(&self) -> u32 {
        click_upgrade_bonus(self.click_upgrade_level)
    }

    fn buy_click_upgrade(&mut self) -> PurchaseOutcome {
        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold < cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
}

impl Enemy {
    fn for_stage(stage: u32) -> Enemy {
        Enemy {
            name: String::from("Training Slime"),
            health: enemy_health_for_stage(stage),
            gold_reward: gold_reward_for_stage(stage),
            xp_reward: xp_reward_for_stage(stage),
        }
    }
}

impl Game {
    fn new() -> Game {
        Game {
            stage: 1,
            player: Player::new(),
            enemy: Enemy::for_stage(1),
        }
    }

    fn click(&mut self) {
        attack(&self.player, &mut self.enemy);

        if is_defeated(self.enemy.health) {
            self.defeat_enemy();
        }
    }

    fn defeat_enemy(&mut self) {
        self.player.add_gold(self.enemy.gold_reward);
        self.player.add_xp(self.enemy.xp_reward);

        self.stage = self.stage.saturating_add(1);
        self.enemy = Enemy::for_stage(self.stage);
    }

    fn buy_click_upgrade(&mut self) -> PurchaseOutcome {
        self.player.buy_click_upgrade()
    }
}

fn click_damage(base: u32, bonus: u32) -> u32 {
    base.saturating_add(bonus)
}

fn health_after_hit(health: u32, damage: u32) -> u32 {
    health.saturating_sub(damage)
}

fn is_defeated(health: u32) -> bool {
    health == 0
}

fn enemy_health_for_stage(stage: u32) -> u32 {
    stage.saturating_mul(10).saturating_add(10)
}

fn gold_reward_for_stage(stage: u32) -> u32 {
    stage.saturating_mul(2).saturating_add(3)
}

fn xp_reward_for_stage(stage: u32) -> u32 {
    stage.saturating_mul(2).saturating_add(4)
}

fn xp_to_next_level(level: u32) -> u32 {
    level
        .saturating_sub(1)
        .saturating_mul(15)
        .saturating_add(20)
}

fn base_damage_for_level(level: u32) -> u32 {
    level.saturating_add(4)
}

fn click_upgrade_bonus(upgrades: u32) -> u32 {
    upgrades.saturating_mul(2)
}

fn click_upgrade_cost(upgrades: u32) -> u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}

fn attack(player: &Player, enemy: &mut Enemy) {
    let damage = click_damage(player.base_damage(), player.bonus_damage());

    enemy.health = health_after_hit(enemy.health, damage);
}

fn main() {
    let mut game = Game::new();

    while game.stage <= 8 {
        let stage = game.stage;
        let level = game.player.level;

        game.click();

        if game.stage > stage {
            println!(
                "Stage {stage} cleared!  Gold: {}  XP: {}/{}",
                game.player.gold,
                game.player.xp,
                xp_to_next_level(game.player.level)
            );

            if game.player.level > level {
                println!(
                    "Level up! Now level {}. Click damage: {}",
                    game.player.level,
                    click_damage(game.player.base_damage(), game.player.bonus_damage())
                );
            }

            println!(
                "  Next up: {} with {} health.",
                game.enemy.name, game.enemy.health
            );
        }

        match game.buy_click_upgrade() {
            PurchaseOutcome::Purchased => println!(
                "Bought click upgrade {}! Click damage: {}  Gold left: {}",
                game.player.click_upgrade_level,
                click_damage(game.player.base_damage(), game.player.bonus_damage()),
                game.player.gold
            ),
            PurchaseOutcome::NotEnoughGold => {}
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn click_damage_adds_base_and_bonus() {
        assert_eq!(click_damage(3, 4), 7);
    }

    #[test]
    fn health_never_drops_below_zero() {
        assert_eq!(health_after_hit(3, 10), 0);
    }

    #[test]
    fn zero_health_is_defeated() {
        assert!(is_defeated(0));
        assert!(!is_defeated(1));
    }

    #[test]
    fn attack_reduces_enemy_health() {
        let player = Player::new();

        let mut enemy = Enemy::for_stage(1);
        enemy.health = 10;

        attack(&player, &mut enemy);

        assert_eq!(enemy.health, 5);
    }

    #[test]
    fn later_stages_have_tougher_enemies() {
        assert_eq!(enemy_health_for_stage(1), 20);
        assert_eq!(enemy_health_for_stage(2), 30);
    }

    #[test]
    fn later_stages_pay_better() {
        assert_eq!(gold_reward_for_stage(1), 5);
        assert_eq!(gold_reward_for_stage(2), 7);
        assert_eq!(xp_reward_for_stage(1), 6);
        assert_eq!(xp_reward_for_stage(2), 8);
    }

    #[test]
    fn levels_need_more_xp_as_they_go() {
        assert_eq!(xp_to_next_level(1), 20);
        assert_eq!(xp_to_next_level(2), 35);
    }

    #[test]
    fn leveling_up_keeps_leftover_xp() {
        let mut player = Player::new();

        player.add_xp(25);

        assert_eq!(player.level, 2);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn a_big_reward_can_skip_levels() {
        let mut player = Player::new();

        player.add_xp(60);

        assert_eq!(player.level, 3);
        assert_eq!(player.xp, 5);
    }

    #[test]
    fn levels_make_the_player_hit_harder() {
        assert_eq!(base_damage_for_level(1), 5);
        assert_eq!(base_damage_for_level(2), 6);
    }

    #[test]
    fn upgrades_cost_more_each_time() {
        assert_eq!(click_upgrade_cost(0), 10);
        assert_eq!(click_upgrade_cost(1), 20);
    }

    #[test]
    fn buying_an_upgrade_needs_gold() {
        let mut player = Player::new();

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::NotEnoughGold);
        assert_eq!(player.click_upgrade_level, 0);
        assert_eq!(player.bonus_damage(), 0);
    }

    #[test]
    fn buying_an_upgrade_spends_gold() {
        let mut player = Player::new();
        player.gold = 12;

        assert_eq!(player.buy_click_upgrade(), PurchaseOutcome::Purchased);
        assert_eq!(player.gold, 2);
        assert_eq!(player.click_upgrade_level, 1);
        assert_eq!(player.bonus_damage(), 2);
    }

    #[test]
    fn a_new_game_starts_at_stage_one() {
        let game = Game::new();

        assert_eq!(game.stage, 1);
        assert_eq!(game.enemy.health, 20);
        assert_eq!(game.player.gold, 0);
    }

    #[test]
    fn clicking_damages_the_enemy() {
        let mut game = Game::new();

        game.click();

        assert_eq!(game.enemy.health, 15);
    }

    #[test]
    fn defeating_an_enemy_pays_the_player() {
        let mut game = Game::new();
        game.enemy.health = 1;

        game.click();

        assert_eq!(game.stage, 2);
        assert_eq!(game.enemy.health, 30);
        assert_eq!(game.player.gold, 5);
        assert_eq!(game.player.xp, 6);
    }
}
</code></pre>
</details>

Run both:

```bash
cargo run
cargo test
```

The game should clear eight stages, buying three upgrades along the way, and all sixteen tests should pass.

---

## Your turn: cap the upgrades

A third way for a purchase to fail is exactly what the enum was built for. This is your chance to watch Rust walk you to every place that needs to know.

**Coding challenge: a maxed-out upgrade**

Stop selling click upgrades after the fifth one.

1. Add a third variant to `PurchaseOutcome`, for an upgrade that is maxed out.
2. In `buy_click_upgrade()`, return that variant when the player already has 5 upgrades. Check this before the price.
3. Press Run. Rust will refuse to compile until the `match` in `main()` handles your new variant. Add that arm.

Keep `Player`, its two fields and `buy_click_upgrade()` as they are, because the checks call them. **Check my code** will tell you how you are doing.

**Expected result:** five "Bought upgrade" lines, then two lines saying the upgrades are maxed out. Gold left stops at 850.

**Coding challenge · cap the upgrades**

```rust
struct Player {
    gold: u32,
    click_upgrade_level: u32,
}

enum PurchaseOutcome {
    Purchased,
    NotEnoughGold,
    // TODO 1: add a third variant for "this upgrade is maxed out"
}

impl Player {
    fn buy_click_upgrade(&mut self) -> PurchaseOutcome {
        // TODO 2: stop at 5 upgrades, before checking the price

        let cost = click_upgrade_cost(self.click_upgrade_level);

        if self.gold < cost {
            return PurchaseOutcome::NotEnoughGold;
        }

        self.gold -= cost;
        self.click_upgrade_level = self.click_upgrade_level.saturating_add(1);

        PurchaseOutcome::Purchased
    }
}

fn click_upgrade_cost(upgrades: u32) -> u32 {
    upgrades.saturating_add(1).saturating_mul(10)
}

fn main() {
    let mut player = Player {
        gold: 1000,
        click_upgrade_level: 0,
    };

    for _ in 0..7 {
        match player.buy_click_upgrade() {
            PurchaseOutcome::Purchased => println!(
                "Bought upgrade {}. Gold left: {}",
                player.click_upgrade_level, player.gold
            ),
            PurchaseOutcome::NotEnoughGold => println!("Not enough gold."),
            // TODO 3: tell the player the upgrade is maxed out
        }
    }
}

```

<details>
<summary>Spoiler: the checks for this challenge</summary>
<p>These tests run when you press Check my code on the original article. Reading them first gives away the answer.</p>
<pre><code class="language-rust">/// Once a player holds 5 upgrades, buying again should leave their gold and level alone. Look at what your function does at the cap.
#[test]
fn sixth_upgrade_is_refused() {
    let mut player = Player {
        gold: 1000,
        click_upgrade_level: 5,
    };
    player.buy_click_upgrade();
    assert_eq!(player.gold, 1000);
    assert_eq!(player.click_upgrade_level, 5);
}

/// A player at the cap with an empty purse is maxed out, not broke. Which check comes first?
#[test]
fn cap_is_checked_before_price() {
    let mut player = Player {
        gold: 0,
        click_upgrade_level: 5,
    };
    let outcome = player.buy_click_upgrade();
    assert!(!matches!(outcome, PurchaseOutcome::NotEnoughGold));
    assert!(!matches!(outcome, PurchaseOutcome::Purchased));
}

/// The fifth upgrade still has to sell. Look at where your cap starts.
#[test]
fn fifth_upgrade_still_sells() {
    let mut player = Player {
        gold: 1000,
        click_upgrade_level: 4,
    };
    let outcome = player.buy_click_upgrade();
    assert!(matches!(outcome, PurchaseOutcome::Purchased));
    assert_eq!(player.click_upgrade_level, 5);
    assert_eq!(player.gold, 950);
}
</code></pre>
</details>

[Run and edit this example on the canonical article](https://journeytocode.io/rust-enums-and-match-click-upgrades/).

> **Tip:** Step 2 is an early `return`, just like the `NotEnoughGold` check below it. Compare `self.click_upgrade_level` with 5 using `>=`.

<details>
<summary>Show one solution</summary>
<p>The new variant:</p>
<pre><code class="language-rust">    MaxedOut,
</code></pre>
<p>The early return, at the top of <code>buy_click_upgrade()</code>:</p>
<pre><code class="language-rust">        if self.click_upgrade_level >= 5 {
            return PurchaseOutcome::MaxedOut;
        }
</code></pre>
<p>The new arm in <code>main()</code>:</p>
<pre><code class="language-rust">            PurchaseOutcome::MaxedOut => println!("Click upgrades are maxed out."),
</code></pre>
</details>

The challenge is its own small program, so your `src/main.rs` stays exactly as it is above.

---

## What we learned

Along the way, we encountered:

- a second rule-derived value, replacing a stored one
- early `return`
- `enum` and variants
- traits, in one sentence, and `#[derive(Debug, PartialEq)]`
- `match`, and the compiler refusing to let us forget a case

Two ideas are worth keeping.

The first finishes the job `base_damage()` started. `Player` has no damage numbers in it at all now. It stores progress, and damage is a rule applied to that progress.

The second is `PurchaseOutcome`.

When a `bool` would have made the caller remember what `false` meant, we gave the answers names instead, and Rust started checking that every answer gets handled.

That pattern shows up everywhere in Rust. Two common examples you will eventually meet are `Option`, for a value that might not be there, and `Result`, for an operation that might have failed.

This is the first time we needed our own. It will not be the last.

---

## Nothing happens unless you click

Our game finally has a progression loop.

Kill a slime, get paid, level up, buy damage, kill a tougher slime.

There is just one problem with calling it an idle game.

```text
clicks per second while you are playing: a lot
clicks per second while you are not:    zero
```

Nothing happens unless you click.

Walk away, come back an hour later, and the slime is exactly where you left it.

An idle game is supposed to keep working while you do not.

Next time, we are going to give the game time.

Not a timer. Not a clock. Just a number: how many seconds went by.

The game will take that number and work out everything that should have happened.

**Small function. Bigger game.** [Join the discussion on the canonical article](https://journeytocode.io/rust-enums-and-match-click-upgrades/)
