# Rust Programming Language: Simple Game

- Published: March 8, 2023
- Author: William Kennedy
- URL: https://www.ardanlabs.com/blog/2023/03/rust-programming-language-simple-game.html

**with Cheikh Seck**

### Introduction

This is the first in a series of posts that will explore the Rust programming language. I am going to take the same approach I did with Go and write little programs that explore the different features of the language. Before I begin that work, I thought it would be fun to write a program that does something fun.

In this post and my first ever Rust program, I’m going to use a Rust library called [bracket-lib](https://github.com/amethyst/bracket-lib) that provides a cross-platform terminal emulator. I will use this library to build a simple game window that will allow me to animate a box that can be moved up, with gravity causing it to move back down.

All the code in this post can be found on Github at this [link](https://github.com/ardanlabs/rusttraining/tree/main/blogs/post-01).

### Installation

To start, you need the Rust front end tool called `cargo`. If you are new to Rust and don’t have it installed yet, you can follow these [instructions](https://www.rust-lang.org/tools/install).

If you need help with your editor environment there are plugins for the different editors.

For VSCode these are two plugins that I am using:

https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer  
https://marketplace.visualstudio.com/items?itemName=statiolake.vscode-rustfmt

For any of the JetBrains products, you have these options:

IDE IntelliJ: https://www.jetbrains.com/rust/  
JetBrains: https://plugins.jetbrains.com/plugin/8182-rust

### Create a New Rust Project

Before you can do anything, you need to initialize a new Rust program.

**Listing 1:**
```
$ cargo new simple-game
```

In listing 1, you can see the use of cargo to initialize a new Rust program called `simple-game`. This will result in the creation of a few files.

**Listing 2:**
```
.
├── Cargo.lock
├── Cargo.toml
├── src
│   ├── main.rs
```

Listing 2 shows the files that should be created after running the cargo command.

_Note: If you’re not in a git repo when you run "cargo new", the command also gives you a .gitignore file and creates a repo for you. (Unless you specify "--vcs none")_

Now the bracket-lib dependency needs to be added to the project.

**Listing 3:**
```
$ cargo add bracket-lib

    Updating crates.io index
      Adding bracket-lib ~0.8 to dependencies.
             Features as of v0.8.0:
             + opengl
             - amethyst_engine_metal
             - amethyst_engine_vulkan
             - crossterm
             - curses
             - serde
             - specs
             - threaded
```

Listing 3 shows how to use the `cargo add` command to search the [crate list](https://crates.io/) for bracket-lib and insert the dependency into the project.

**Listing 4:**
```
01 [package]
02 name = "simple-game"
03 version = "0.1.0"
04 edition = "2021"
05 
06 # See more keys and their definitions at https://doc.rust-lang.org/cargo
07
08 [dependencies]
09 bracket-lib = "~0.8"
```

In listing 4, you can see the `cargo.toml` file and the entry added to line 09 by the cargo add command. This line will add bracket-lib as a dependency for the program.

### Main Function

With the Rust program initialized and the bracket-lib dependency configured, writing the code for the game can begin.

Open the `main.rs` file found in the `src/` folder.

**Listing 5:**
```
01 use bracket_lib::terminal::{main_loop, BTermBuilder};
02 mod state;
03
04 fn main() {
05     let result = BTermBuilder::simple80x50()
06         .with_title("Hello Bracket World")
07         .build();
08
09     match result {
10         Ok(bterm) => {
11             let _ = main_loop(bterm, state::new());
12         }
13         Err(err) => {
14             println!("An error has occurred: {err:?}");
15             println!("Aborting");
16         }
17     }
18 }
```

Listing 5 shows all the code needed for the main function. On line 01, two identifiers from the bracket-lib library are imported into the global namespace. Then on line 02, the `state` module (which we have not written yet) is made accessible to this source code file. The `state` module will live inside the project and will contain all of the functionality required by the game.

_Note: By selecting the exact identifiers you need in a `use` statement, you can help compile times a bit (reducing the number of identifiers to lookup). It can also help prevent collisions with identifiers being named the same in different dependencies. However, this is mostly done to keep things tidy._

On line 05, the code builds a game window using the `simple80x50` function from the bracket-lib dependency. The call to the `build` function on line 08 returns a [result value](https://doc.rust-lang.org/std/result/) and the code on lines 09 through 17 checks the result for success or failure. 

The best way to explain how the error handling works in Rust is to look at this new example.

**Listing 6:**
```
01 struct User {
02     name: String,
03     age: isize,
04 }
05
06 struct Error {
07     msg: String,
08 }
09
10 fn query_user(worked: bool) -> Result<User, Error> {
11     if worked {
12         return Ok(User {
13             name: "Bill".to_string(),
14             age: 53,
15         });
16     }
17
18     return Err(Error {
19         msg: "unable to fetch user".to_string(),
20     });
21 }
22
23 fn main() {
24     match query_user(true) {
25         Ok(usr) => println!("Name:{0} Age:{1}", usr.name, usr.age),
26         Err(err) => println!("{0}", err.msg),
27     }
28
29     match query_user(false) {
30         Ok(usr) => println!("Name:{0} Age:{1}", usr.name, usr.age),
31         Err(err) => println!("{0}", err.msg),
32     }
33 

```

Listing 6 shows a new program that can best explain how error handling works in Rust. On lines 01 through 08, two structs are declared. The `User` struct represents a user in the system and the `Error` struct represents a general error type. On lines 10 through 21, a function named `query_user` is declared that accepts a boolean value and returns a value of type `Result`. This [type](https://doc.rust-lang.org/std/result/) is provided by the standard library and is actually an enumeration.

_Note: I had two Rust developers tell me it’s more idiomatic in Rust to use an if statement for a boolean check. I avoid else statements at all costs, and use switch statements in Go and Javascript for the same situation. I believe writing conditional logic without an else is more readable._

**Listing 7:**
```
enum Result<T, E> {
   Ok(T),
   Err(E),
}
```

In listing 7 you can see how `Result` is declared and how a value of type `Result` can either be `Ok` or `Err`. In either case, a value of some type `T` or `E` can be passed respectively.

**Listing 8:**
```
10 fn query_user(worked: bool) -> Result<User, Error> {
11     if worked {
12         return Ok(User {
13             name: "Bill".to_string(),
14             age: 53,
15         });
16     }
17
18     return Err(Error {
19         msg: "unable to fetch user".to_string(),
20     });
21 }
```

In listing 8 you see the `query_user` function again. On line 11, an if statement is used to check if the `worked` parameter is true. If the variable is true, then the `result` variable is assigned a value of `Ok` with a value of type `User`. If the `worked` variable is false, then the `result` variable is assigned a value of `Err` with a value of type `Error`.

**Listing 9:**
```
23 fn main() {
24     match query_user(true) {
25         Ok(usr) => println!("Name:{0} Age:{1}", usr.name, usr.age),
26         Err(err) => println!("{0}", err.msg),
27     }
28
29     match query_user(false) {
30         Ok(usr) => println!("Name:{0} Age:{1}", usr.name, usr.age),
31         Err(err) => println!("{0}", err.msg),
32     }
33 }
```

Finally in listing 9, calls to the `query_user` function are made on lines 24 and 29. A `match` statement is used once more to compare which value, either `Ok` or `Err`, was returned by the `query_user` function. Depending on the returned value, a user value or error is printed.

Now back to the original program.

**Listing 10:**
```
01 use bracket_lib::terminal::{main_loop, BTermBuilder};
02 mod state;
03
04 fn main() {
05     let result = BTermBuilder::simple80x50()
06         .with_title("Hello Bracket World")
07         .build();
08
09     match result {
10         Ok(bterm) => {
11             let _ = main_loop(bterm, state::new());
12         }
13         Err(err) => {
14             println!("An error has occurred: {err:?}");
15             println!("Aborting");
16         }
17     }
18 }
```

In the case of the original program, the result from the call to `build` is handled with a `match` statement on line 09. Like the previous sample program, the values of `Ok` and `Err` are compared. In this case the `build` function is returning a `Result` value declared like this.

**Listing 11:**
```
Result<BTerm, Box<dyn Error + Send + Sync>>
```

Listing 11 shows a `BTerm` value is provided for the `Ok` case and a `Box` value is provided for the `Err` case. Now depending on what is matched, the main function either runs the `main_loop` function from the bracket-lib library or prints the error information.

### State Module

**Listing 12:**
```
.
├── Cargo.lock
├── Cargo.toml
├── src
│   ├── main.rs
│   └── state
│       └── mod.rs
```

Listing 12 shows how a new folder named `state/` is added to the `src/` folder and then a file named `mod.rs` is created. When creating a new module, the core source code file needs to be named `mod.rs` so the compiler can recognize this folder as a module and the `pub` keyword (that we used in the main function) can be used to make the module accessible when needed.

**Listing 13:**
```
01 //! State contains all the game state and logic.
```

Listing 13 shows the first line of code in the `mod.rs` source code file. The program starts out with a comment using `//!`, which is used to indicate this comment needs to be part of the module's documentation.

If you want to see the program’s documentation, you can run the cargo doc command.

**Listing 14:**
```
$ cargo doc --no-deps --open
```

Then the following webpage should open in your default browser.

**Figure 1**
![](../../../images/goinggo/176_figure1.png)

If you want to learn more about writing documentation, this [post](https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/documentation.html) provides nice detailed examples.

**Listing 15:**
```
03 use bracket_lib::prelude::*;
04
05 const GAME_WINDOW_HEIGHT: isize = 50;
06 const GAME_WINDOW_WIDTH: isize = 80;
07 const BOX_HEIGHT: isize = 5;
08 const BOX_WIDTH: isize = 5;
09 const CEILING_POS: isize = 5;
10 const FLOOR_POS: isize = 45;
11 const CEILING_COLLISION: isize = CEILING_POS + 1;
12 const FLOOR_COLLISION: isize = FLOOR_POS - BOX_HEIGHT - 1;
```

In listing 15, the code imports the bracket-lib library again and then defines 8 constants that represent different hard coded values for the game. The idiom for constants is to use capital letters and underscores. You can see the type information follows the identifier’s name, just like in Go. Rust has all the same precision based integer types, but defines `isize` and `usize` as the architecture-dependent integer size, which match the size of an address.

Unfortunately, the Rust format program does not align types and assignments like the Go format tooling does. :(

**Listing 16:**
```
14 /// Moving represents the set of possible moving options.
15 enum Moving {
16     Not,
17     Up,
18     Down,
19 }
```

The next section of code in listing 16 defines an enumeration. The comment with the three slashes indicates this comment should be part of the module’s documentation for this identifier. The three values declared for this enumeration will represent a direction the game block could be moving. The idiom for naming an enumeration is to use [CamelCase](https://en.wikipedia.org/wiki/Camel_case). 

**Listing 17:**
```
23 /// State represents the game state for the game.
24 pub struct State {
25     box_y: isize,       // Box's vertical position.
26     box_moving: Moving, // Direction the box is moving.
27 }
```

In listing 17, a struct type is declared named `State`. The idiom for naming a struct type is to use CamelCase like it was with the enumeration, however the idiom for naming a field name is to use [snake_case](https://en.wikipedia.org/wiki/Snake_case).

The struct type is marked as public using the `pub` access specifier. This will allow other modules (like main) to have access to values of this type. However, the two fields that are declared on lines 25 and 26 are not marked as `pub` and will remain private and only accessible to code inside the `state` module.

_Note: The three slash comments do not work if used on the side of a field declaration like you see in listing 17._

**Listing 18:**
```
29 /// new constructs a new game state.
30 pub fn new() -> State {
31     return State {
32         box_y: FLOOR_COLLISION,
33         box_moving: Moving::Not,
34     };
35 }
```

Since the construction of a `State` value requires specific initialization, a factory function is declared with the name `new` in listing 18. The idiom for function names is to use snake_case like it was with field names. The syntax for a function declaration is fairly obvious except for the use of an arrow operator to define the return argument. Functions in Rust can return multiple values using [tuples](https://doc.rust-lang.org/rust-by-example/primitives/tuples.html).

### Traits

**Listing 19:**
```
37 /// State implementation of the GameState trait.
38 impl GameState for State {
39     fn tick(&mut self, bterm: &mut BTerm) {
40         self.keyboard_input(bterm);
41         self.render(bterm);
42     }
43 }
```

In listing 19, you can see a declaration for the `State` type to implement the  `GameState` [trait](https://doc.rust-lang.org/book/ch10-02-traits.html) declared by bracket-lib. 

_Note: Traits are similar to a feature often called interfaces in other languages, although with some differences._

The bracket-lib library is going to provide the GUI framework for the game and requires the implementation of a function named `tick`. It’s this `tick` function that bracket-lib will call every time it wants to render the screen. The `GameState` trait acts like an interface and declares the required behavior the library needs to have implemented.

On line 39, you see the declaration of the `tick` function needed to satisfy the trait. This is the only function declared by the `GameState` trait.

**Listing 20:**
```
01 pub use crate::prelude::BTerm;
02
03 /// Implement this trait on your state struct, so the engine knows what...
04 pub trait GameState: 'static {
05     fn tick(&mut self, ctx: &mut BTerm);
06 }
```

Listing 20 shows the declaration of the `GameState` trait provided by bracket-lib. You can see there is only one function named `tick` that has two parameters. The first parameter named `self` is the receiver and will contain a pointer to the value implementing the trait. The second parameter is named `ctx` and will contain a pointer to a value that represents the game window. The first parameter must be named `self` and requires no type declaration.

It’s the use of the `&` [operator](https://doc.rust-lang.org/std/primitive.reference.html) that declares these parameters are using pointer semantics. The `&` operator is often called a “borrow” in Rust. The use of the keyword `mut` means the variable can be used to mutate the value. If you leave out `mut`, then the variable is read only.

_Note: Another useful way to refer to `&mut` is to call it an "exclusive" reference. Only one entity can have access to an exclusive reference at a time in Rust, and this is guaranteed by the compiler. This is one of the things that makes Rust safe._

**Listing 21:**
```
37 /// State implementation of the GameState trait.
38 impl GameState for State {
39     fn tick(&mut self, bterm: &mut BTerm) {
40         self.keyboard_input(bterm);
41         self.render(bterm);
42     }
43 }
```

If you notice on line 39 in listing 21, I changed the name of the `ctx` parameter to `bterm` in my function declaration. I am doing this to be more specific with what that value represents.

The implementation of the `tick` function performs two method calls against the `State` value that is represented by `self`. The first method is named `keyboard_input` and it checks to see if the space bar was pressed. The second method is named `render` and it renders the screen with any changes that were made to the game state.

**Listing 22:**
```
 45 /// Method set for the State type.
 46 impl State {
 47     /// keyboard_input handles the processing of keyboard input.
 48     fn keyboard_input(&mut self, bterm: &mut BTerm) {
 58     }
 59
 60     /// render takes the current game state and renders the screen.
 61     fn render(&mut self, bterm: &mut BTerm) {
133     }
134 }
```

On line 46 in listing 22, the keyword `impl` is used to implement a method-set of functions for the `State` type. You can see the declaration of the two methods that were called in listing 21 by the `tick` function. Once again, the first parameter is a pointer to the `State` value that was constructed when the game started and the second parameter is a pointer to the game window.

**Listing 23:**
```
47     /// keyboard_input handles the processing of keyboard input.
48     fn keyboard_input(&mut self, bterm: &mut BTerm) {
49         match bterm.key {
50             None => {}
51             Some(VirtualKeyCode::Space) => {
52                 if self.box_y == FLOOR_COLLISION {
53                     self.box_moving = Moving::Up;
54                 }
55             }
56             _ => {}
57         };
58     }
```

Listing 23 shows the implementation of the `keyboard_input` function. The function uses a `match` statement to compare the `key` field from the `bterm` value to see if the spacebar has been hit. The `key` field is an enumeration of type [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html) and can represent one of two values, `None` or `Some`.

If a `None` value is returned, then there is no pending user input. If a `Some` value is returned and the key that was pressed was the spacebar, that case is executed. The `Some` value has been decalred to support comparing an enumeration value of type `VirtualKeyCode`.  The blank identifier case on line 56 is the default case and is required in this `match` because there are 159 other declarations of `Some` accepting different types that could be used.

The code in the `Some` case checks to see if the box is on the ground and changes the `box_moving` field to moving up if that’s true.

**Listing 24:**
```
60 /// render takes the current game state and renders the screen.
61     fn render(&mut self, bterm: &mut blib::BTerm) {
62         bterm.cls_bg(WHITE);
```

Listing 24 shows the implementation of the `render` function. The code on line 62 clears the entire game window by painting it white.

**Listing 25:**
```
64     bterm.draw_bar_horizontal(
65         0,                  // x
66         CEILING_POS,        // y
67         GAME_WINDOW_WIDTH,  // width
68         GAME_WINDOW_HEIGHT, // n
69         GAME_WINDOW_HEIGHT, // max
70         YELLOW,             // foreground color
71         YELLOW,             // background color
72     );
73
74     bterm.draw_bar_horizontal(
75         0,                  // x
76         GROUND_PIXEL,       // y
77         GAME_WINDOW_WIDTH,  // width
78         GAME_WINDOW_HEIGHT, // n
79         GAME_WINDOW_HEIGHT, // max
80         YELLOW,             // foreground color
81         YELLOW,             // background color
82     );
```

The code in listing 25 draws two yellow lines that represent the ceiling and the floor. 

**Listing 26:**
```
84     bterm.draw_box_double(
85         (GAME_WINDOW_WIDTH / 2) - 3, // x
86         self.box_y,                  // y
87         BOX_WIDTH,                   // width
88         BOX_WIDTH,                   // height
89         RED,                         // foreground color
90         RED,                         // background color
91     );
```

The code in listing 26 draws the game box that the player will maneuver. The box is centered in the middle of the game window.

**Listing 27:**
```
 93     match self.box_moving {
 94         Moving::Down => {
 95             self.box_y += 1;
 96             if self.box_y == FLOOR_COLLISION {
 97                 self.box_moving = Moving::Not;
 98             }
 99         }
100         Moving::Up => {
101             self.box_y -= 1;
102             if self.box_y == CEILING_COLLISION {
103                 self.box_moving = Moving::Down;
104             }
105         }
106         Moving::Not => {}
107     }
```

Listing 27 shows the final code for the program. This code checks the position of the game box and changes its position depending on the direction it’s moving. If the game box hits the ground, the box is set to stop moving. If the game box hits the ceiling, the box is set to fall back down.

### Running The Game

To launch the game, run `cargo run` at the root of the project’s folder. Any dependencies that are missing will be automatically downloaded into a folder named `target`.

**Listing 28:**
```
$ cargo run

    Finished dev [unoptimized + debuginfo] target(s) in 0.05s
     Running `target/debug/simple-game`
Initialized OpenGL with: 4.1 Metal - 83, Shader Language Version: 4.10
```

**Figure 2**
![](../../../images/goinggo/176_figure2.png)

Figure 2 shows you the game window and the red box. Remember to use the spacebar to cause the game box to move up towards the ceiling. Then watch the box automatically fall back down.

_Note: The executable (statically linked, including all dependencies) will be in `target/debug` (name varies by platform; e.g. on this Windows box it's a `.exe`). Make sure you have the target folder in your `.gitignore` You don't want to be committing your build artifacts._

### Conclusion

This was my first post in a series where I will begin to explore the Rust programming language. This was a fun program to start with since it allowed me to begin to understand how types, assignments, idioms, method-sets, error handling, and traits work. These are all things I want to explore in more detail. Hopefully you were able to run the program and begin to understand Rust syntax.
