Programming a Guessing Game

In this chapter, we'll explore build a hands-on project to learn mana fundamentals.

Programming a Guessing Game

Let's learn Mana by building a classic beginner project: a guessing game. Here's how it works: the program generates a random number between 1 and 100, then prompts the player to guess. After each guess, the program indicates whether the guess is too low, too high, or correct.

Setting Up a New Project

Create a new project:

mana new guessing_game
cd guessing_game

Open src/main.mana and replace its contents with:

module main
 
fn main() -> void {
    println("Guess the number!")
}

Run it with mana run to verify everything works.

Processing a Guess

First, let's ask for user input:

module main
 
use std.io
 
fn main() -> void {
    println("Guess the number!")
    println("Please input your guess.")
 
    let mut guess = String::new()
 
    io.stdin()
        .read_line(guess)
        .expect("Failed to read line")
 
    println("You guessed: ", guess)
}

Let's break this down:

Importing with use

use std.io

This brings the io module from the standard library into scope, giving us input/output functionality.

Storing Values with Variables

let mut guess = String::new()

This creates a mutable variable named guess bound to a new, empty string. The mut keyword makes it mutable—without it, variables are immutable by default.

Receiving User Input

io.stdin()
    .read_line(guess)
    .expect("Failed to read line")

We call stdin() to get a handle to standard input, then read_line() to read a line into our string. The expect() handles potential errors—if reading fails, it prints our message and exits.

Variadic Print

println("You guessed: ", guess)

Mana's println accepts multiple arguments of any type. The values are printed in sequence.

Generating a Secret Number

Now let's generate a random number. Add the rand dependency to mana.toml:

[dependencies]
rand = "0.8"

Update src/main.mana:

module main
 
use std.io
use rand
 
fn main() -> void {
    println("Guess the number!")
 
    let secret_number = rand.gen_range(1, 101)
 
    println("The secret number is: ", secret_number)
 
    println("Please input your guess.")
 
    let mut guess = String::new()
 
    io.stdin()
        .read_line(guess)
        .expect("Failed to read line")
 
    println("You guessed: ", guess)
}

We generate a random number between 1 and 100 (the range is exclusive on the upper bound, so we use 101).

Comparing the Guess to the Secret Number

Now let's compare the guess to the secret number:

module main
 
use std.io
use rand
 
fn main() -> void {
    println("Guess the number!")
 
    let secret_number = rand.gen_range(1, 101)
 
    println("Please input your guess.")
 
    let mut guess = String::new()
 
    io.stdin()
        .read_line(guess)
        .expect("Failed to read line")
 
    // Convert string to number
    let guess: int = guess.trim().parse()
        .expect("Please type a number!")
 
    println("You guessed: ", guess)
 
    match guess.cmp(secret_number) {
        Ordering.Less => println("Too small!"),
        Ordering.Greater => println("Too big!"),
        Ordering.Equal => println("You win!"),
    }
}

Converting Types

let guess: int = guess.trim().parse()
    .expect("Please type a number!")

We shadow the original guess variable with a new one. The trim() removes whitespace (including the newline from pressing Enter), and parse() converts the string to a number.

Pattern Matching

match guess.cmp(secret_number) {
    Ordering.Less => println("Too small!"),
    Ordering.Greater => println("Too big!"),
    Ordering.Equal => println("You win!"),
}

The match expression compares values against patterns. Here, cmp returns an Ordering value, and we handle each possibility.

Allowing Multiple Guesses with Looping

Let's add a loop so players can guess multiple times:

module main
 
use std.io
use rand
 
fn main() -> void {
    println("Guess the number!")
 
    let secret_number = rand.gen_range(1, 101)
 
    loop {
        println("Please input your guess.")
 
        let mut guess = String::new()
 
        io.stdin()
            .read_line(guess)
            .expect("Failed to read line")
 
        let guess: int = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue
        }
 
        println("You guessed: ", guess)
 
        match guess.cmp(secret_number) {
            Ordering.Less => println("Too small!"),
            Ordering.Greater => println("Too big!"),
            Ordering.Equal => {
                println("You win!")
                break
            }
        }
    }
}

Quitting After a Correct Guess

When the player guesses correctly, we print a congratulatory message and break out of the loop.

Handling Invalid Input

Instead of crashing on invalid input, we use match on the parse() result:

  • On Ok(num), we extract and return the number
  • On Err(_), we continue to the next iteration, ignoring the invalid input

The Complete Game

Here's our finished guessing game:

module main
 
use std.io
use rand
 
fn main() -> void {
    println("Guess the number!")
 
    let secret_number = rand.gen_range(1, 101)
 
    loop {
        println("Please input your guess.")
 
        let mut guess = String::new()
 
        io.stdin()
            .read_line(guess)
            .expect("Failed to read line")
 
        let guess: int = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println("Please enter a valid number!")
                continue
            }
        }
 
        println("You guessed: ", guess)
 
        match guess.cmp(secret_number) {
            Ordering.Less => println("Too small!"),
            Ordering.Greater => println("Too big!"),
            Ordering.Equal => {
                println("You win!")
                break
            }
        }
    }
}

Summary

This project introduced many Mana concepts:

  • module declarations
  • let, mut, and variables
  • use statements for importing
  • Functions like println(), stdin(), read_line()
  • Variadic print with multiple arguments
  • Type conversion with parse()
  • match expressions for pattern matching
  • loop, break, and continue for iteration
  • Error handling with Result (Ok/Err)

Congratulations! You've built your first Mana program. Now let's explore these concepts in more depth in the following chapters.