= Graphics :icons: font :source-highlighter: pygments :source-language: rust ifdef::env-github[:outfilesuffix: .adoc] :rust: https://www.rust-lang.org/ :tcod-rs: https://github.com/tomassedovic/tcod-rs :console: http://tomassedovic.github.io/tcod-rs/tcod/console/index.html :colors: http://tomassedovic.github.io/tcod-rs/tcod/colors/index.html :match: https://doc.rust-lang.org/book/ch06-02-match.html :key: http://tomassedovic.github.io/tcod-rs/tcod/input/struct.Key.html <> == Setting it up === Installing Rust To install Rust, go to the {rust}[Rust homepage] and click the `Install` button. === Creating the roguelike project Next we need to create the Rust project that will be our roguelike: ---- $ cargo new --bin roguelike $ cd roguelike/ ---- This will create two files: `Cargo.toml` and `main.rs` in the `src` directory. `Cargo.toml` contains metadata for the project and any dependencies you're going to need (such as libtcod). `main.rs` is the file that contains your program. In it must be a function called `main` which will be executed when you run your program. Cargo has automatically put a hello world program in your `main.rs` file. It looks like this: [source,rust] ---- fn main() { println!("Hello, world!"); } ---- You can run it by typing: [source,bash] ---- cargo run --release ---- You will see something like this: .... Compiling roguelike v0.1.0 (file:///home/thomas/tmp/roguelike) Running `target/release/roguelike` Hello, world! .... Rust will first compile `main.rs` and any other files it needs (there are none yet). The resulting binary program will be called `roguelike` and placed in the `target/release` directory. Cargo will then run the program which will print `Hello, world!`. NOTE: Rust (and Cargo) can compile programs in two modes: _with optimisations_ and _without_. Code compiled without optimisations is *really slow*, but it's easier to work with under a debugger. We will be using the optimised mode in the tutorial (with the `--release` option), to make sure no one is making speed claims without optimisations. If you do need the debug build for something, just drop the `--release` flag. === Adding libtcod You must then install the dependencies for {tcod-rs}[tcod] -- the Rust bindings for libtcod. Go to the following link and follow the instructions for your platform (Windows, Linux or Mac OS X): https://github.com/tomassedovic/tcod-rs#how-to-use-this === Adding the font file We will be using the font `arial10x10.png` that ships with libtcod. You can download it from here: https://github.com/tomassedovic/tcod-rs/raw/master/fonts/arial10x10.png Feel free to check out the other fonts in there and pick the one you like. Be sure to put it in your game directory -- next to `Cargo.toml`! == Showing the @ on screen To use any external library (i.e. code outside of your project) in Rust, you need to have it in `Cargo.toml` under `[dependencies]`: [source,toml] ---- [dependencies] tcod = "0.15" ---- Rust libraries are called _crates_. Next, add some imports via the `use` statements: [source,rust] ---- tag::use_tcod[] use tcod::colors::*; use tcod::console::*; end::use_tcod[] ---- Like in Python, Rust's code can be structured under modules. We could always use the full path to a particular function or type (e.g. ``::tcod::console::Root``), but with the `use` statement, we can bring it up to the current scope. So `use tcod::console::Root` will let us type `Root` directly. Using the asterisk (`*`) as the name means to import everything under `tcod::console`. Which means `Root`, `FontType`, `BackgroundFlag`, etc. Now we'll add some constants so we don't end up littering the source code with a bunch of opaque numbers: [source,rust] ---- tag::consts[] tag::screen_size[] // actual size of the window const SCREEN_WIDTH: i32 = 80; const SCREEN_HEIGHT: i32 = 50; end::screen_size[] tag::limit_fps[] const LIMIT_FPS: i32 = 20; // 20 frames-per-second maximum end::limit_fps[] end::consts[] ---- Just like in Python, constants are written using uppercase (as a convention, not a hard rule). Unlike Python, the `const` keyword guarantees that they will not be changed. As you can see, constants need to have their type specified. In this case it's `i32` or a 32-bit signed integer. Rust is able to infer most types, but they do need to be explicitly mentioned in function definitions and constants. We will also encapsulate all of our libtcod-related values into a single Rust `struct`: [source] ---- tag::tcod_root_struct[] struct Tcod { root: Root, } end::tcod_root_struct[] ---- It only has a single value right now, but we'll add more later on. Having all the tcod stuff in one place makes it easier to pass it around to functions. And doing it now will save us from gnarly refactors later. The Rust compiler would help us there, but it's still an annoying chore we can avoid. And now we can dive into the body of the `main` function and replace the `println!` macro with code that sets up the libtcod window and renders a character on it. To create a window we call `Root::initializer(). ... .init()`. In between go custom settings. Default values are going to be used for any option that's not specified. [source,rust] ---- tag::init[] let root = Root::initializer() .font("arial10x10.png", FontLayout::Tcod) .font_type(FontType::Greyscale) .size(SCREEN_WIDTH, SCREEN_HEIGHT) .title("Rust/libtcod tutorial") .init(); end::init[] tag::tcod_init[] let mut tcod = Tcod { root }; end::tcod_init[] ---- A bunch of stuff is happening here so let's unpack this. Rust uses `let` to create new variables, so `let root = ` will create a new variable called `root`. Variables are immutable by default (so they can't be changed accidentally), but we will want to change the root console (e.g. by writing characters to it). To do that, we need the `mut` keyword. So `let mut tcod = ` means "create a variable called `tcod` that we can change later". As you can see, we don't have to specify the type of the variable -- Rust will figure it out. If we wanted, we could type it explicitly, though: [source,rust] ---- let root: Root = Root::initializer(). ... .init(); ---- Now let's have a look at the window options we're passing in. First, we're setting up a font. Libtcod uses bitmap fonts of various formats. Calling the `font` methods lets us set our own font using a file name and a font layout. `font_type` is another option for loading a font. The font must be in the root of your project, next to `Cargo.toml`. If you've picked a different font than `arial10x10.png`, make sure to put the right filename in your `font` method call. Next we set the window dimensions (width and height in characters) and the text displayed in the window's title bar. Calling `init` will finalise the configuration and actually create the window. [source,rust] ---- tag::set_fps[] tcod::system::set_fps(LIMIT_FPS); end::set_fps[] ---- This line will limit the maximum number of frames per second libtcod will issue. This is useful when you have a realtime game loop. If you block for input (i.e. nothing happens until the player presses a key), it will have no effect. And speaking of game loops, now's the time to add one! Let's render a white `@` on the screen until the libtcod window gets closed: [source,rust] ---- tag::initial_game_loop[] tag::initial_game_loop_header[] tag::game_loop_while_header[] while !tcod.root.window_closed() { end::game_loop_while_header[] tcod.root.set_default_foreground(WHITE); tcod.root.clear(); end::initial_game_loop_header[] tcod.root.put_char(1, 1, '@', BackgroundFlag::None); tag::root_flush[] tcod.root.flush(); end::root_flush[] tcod.root.wait_for_keypress(true); } end::initial_game_loop[] ---- Since we've set the FPS limit, this loop will be executed 20 times a second, no more. The `window_closed` method on the `root` console returns `true` if the window was closed and `false` otherwise. We want to keep going while it's open so we have to use `!` to negate the value. The next line sets a default _foreground_ colour to white. This is the colour everything will be drawn with unless specified otherwise. The `tcod::colors` module contains values for common colours as well as the `Color` struct that lets you use your own. Then we `clear` the console of anything that we drew on the previous frame. Next we draw the `@` character at the coordinates `1, 1` on the screen. The `0, 0` coordinate is at the top left corner of the window. Using `BackgroundFlag::None` says to ignore the default _background_ colour. Calling `flush` will draw everything on the window at once. And finally, we also need to call `wait_for_keypress` even though we're not processing keyboard input yet. This is because libtcod handles the window manager's events (including your request to close the window) in the input processing code. If we didn't call it, `window_close` would not work properly and the game would crash or hang. You can now run it with `cargo run --release` and bask in your creation. It's almost a game now! We will look at input next. Here's link:part-1a-render.rs.txt[the complete code so far]. == Moving around So that was cool. Now let's make our `@` move! We'll need to keep track of the player's position. Let's create variables for `x` and `y` and put them right before the game loop: [source,rust] ---- tag::centre_player_position[] let mut player_x = SCREEN_WIDTH / 2; let mut player_y = SCREEN_HEIGHT / 2; end::centre_player_position[] ---- They are mutable (we will change them when the player presses the arrow keys) and initialised to the centre of the screen instead of the top-left corner. We will split the keyboard handling code into its own function to make our game loop more readable. It will need the `root` console because that's where we read the pressed keys from and also the player's coordinates so we can change them based on the player's actions. [source,rust] ---- tag::handle_keys_full[] tag::handle_keys_header[] fn handle_keys(tcod: &mut Tcod, player_x: &mut i32, player_y: &mut i32) -> bool { end::handle_keys_header[] // todo: handle keys tag::handle_keys_footer[] false } end::handle_keys_footer[] end::handle_keys_full[] ---- A function signature in Rust is `fn function_name(parameter: type, ...) -> return_type`. Here we call our function `handle_keys`; it accepts three parameters -- the root console (of type `tcod::console::Root`), the x coordinate and the y coordinate (of type `i32`) -- and it returns a boolean value. `true` says "exit the game", `false` means "keep going". The `&mut` bits before the types are borrowing operators. You can read about them (and the ownership they're strongly tied to) in the Rust book: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html We must pass `root` as a borrowed value because it would be consumed by the first call to `handle_keys` otherwise. If we just passed `player_x` and `player_y` by value, `handle_keys` could only read their values but it could not change them. Since we want to update them based on the key the player pressed, we'll get them as mutable references. Then we can assign a new value using the dereference operator (e.g. `*player_x = 10`) and that will show up back in the calling scope. Right now, the function's body is empty, except that it always returns `false` (which means, keep the game going). Let's add the keyboard stuff. We use the `wait_for_keypress` method to get the key and then update the player's position if it's one of the arrow keys: [source,rust] ---- tag::match_key_full[] tag::match_key_header[] let key = tcod.root.wait_for_keypress(true); match key { end::match_key_header[] tag::movement_keys[] // movement keys Key { code: Up, .. } => *player_y -= 1, Key { code: Down, .. } => *player_y += 1, Key { code: Left, .. } => *player_x -= 1, Key { code: Right, .. } => *player_x += 1, end::movement_keys[] tag::match_key_footer[] _ => {} } end::match_key_footer[] end::match_key_full[] ---- Instead of chaining a ton of `if/else` expressions together, we use the `match` expression to specify the values we're interested in and what to do with them. The key returned by `wait_for_keypress` is of type `tcod::input::Key` and {key}[has several fields we can look at]. Right now all we care about is the `code`, which tells us the key that was pressed, but there are others for `alt`, `ctrl`, etc. The two dots at the end mean "I don't care about the other fields". If it wasn't there, it would not compile until you specified values for every field of the `Key` struct. Rust requires that `match` arms are _exhaustive_. That means you have to specify all the possible values. However, as we don't care about the other keys the player could possibly press, we can use a special value that matches _everything else_. That's what `_ => {}` at the end does. You can {match}[read more about match in the Rust book]. We could end here, but since we're doing keyboard stuff anyway, let's add two more: `Alt+Enter` to toggle fullscreen mode and `Esc` to exit the game. Put these at the beginning of `match key`: [source,rust] ---- tag::match_special_keys[] Key { code: Enter, alt: true, .. } => { // Alt+Enter: toggle fullscreen let fullscreen = tcod.root.is_fullscreen(); tcod.root.set_fullscreen(!fullscreen); } Key { code: Escape, .. } => return true, // exit game end::match_special_keys[] ---- And finally we need to `use` the keyboard input types we have in the code: [source,rust] ---- tag::use_input_key[] use tcod::input::Key; tag::use_input_keycode[] use tcod::input::KeyCode::*; end::use_input_keycode[] end::use_input_key[] ---- Now, we could put it on top of the file next to the existing imports, but Rust lets you place them in individual functions as well, which will make them available only for that function. Since we'll contain our keyboard-handling code in `handle_keys`, let's make it the first thing there. And finally, we just update the main loop to use our key handling and draw at the player coordinates instead of `(1, 1)`. Put this at the end of the `while` block: [source,rust] ---- tag::handle_exit_keys[] // handle keys and exit game if needed let exit = handle_keys(&mut tcod, &mut player_x, &mut player_y); tag::exit_game_loop[] if exit { break; } end::exit_game_loop[] end::handle_exit_keys[] ---- As you can see, we're passing `tcod` and the coordinates as mutable references. Now update our drawing function to use the player coordinates: [source,rust] ---- tag::player_at_position[] tcod.root .put_char(player_x, player_y, '@', BackgroundFlag::None); end::player_at_position[] ---- Here's link:part-1b-movement.rs.txt[the complete code so far]. Continue to <>.