bracket_terminal::add_wasm_support!(); use bracket_random::prelude::*; use bracket_terminal::prelude::*; // We'll allow map tiles to be either a wall or a floor. We're deriving PartialEq so we don't // have to match on it every time. We'll make it a copy type because it's really just an int. #[derive(PartialEq, Copy, Clone)] enum TileType { Wall, Floor, } // We're extending State to include a minimal map and player coordinates. struct State { map: Vec<TileType>, visited: Vec<bool>, player_position: usize, } // We're storing all the tiles in one big array, so we need a way to map an X,Y coordinate to // a tile. Each row is stored sequentially (so 0..80, 81..160, etc.). This takes an x/y and returns // the array index. pub fn xy_idx(x: i32, y: i32) -> usize { (y as usize * 80) + x as usize } // It's a great idea to have a reverse mapping for these coordinates. This is as simple as // index % 80 (mod 80), and index / 80 pub fn idx_xy(idx: usize) -> (i32, i32) { (idx as i32 % 80, idx as i32 / 80) } // Since we have some content, we should also include a map builder. A 'new' // function is a common Rust way to do this. impl State { pub fn new() -> State { let mut state = State { map: vec![TileType::Floor; 80 * 50], player_position: xy_idx(40, 25), visited: vec![false; 80 * 50], }; // Make the boundaries walls for x in 0..80 { state.map[xy_idx(x, 0)] = TileType::Wall; state.map[xy_idx(x, 49)] = TileType::Wall; } for y in 0..50 { state.map[xy_idx(0, y)] = TileType::Wall; state.map[xy_idx(79, y)] = TileType::Wall; } // Now we'll randomly splat a bunch of walls. It won't be pretty, but it's a decent illustration. // First, obtain the thread-local RNG: let mut rng = RandomNumberGenerator::new(); for _ in 0..400 { // rand provides a gen_range function to get numbers in a range. let x = rng.roll_dice(1, 80) - 1; let y = rng.roll_dice(1, 50) - 1; let idx = xy_idx(x, y); // We don't want to add a wall on top of the player if state.player_position != idx { state.map[idx] = TileType::Wall; } } // We'll return the state with the short-hand state } // Handle player movement. Delta X and Y are the relative move // requested by the player. We calculate the new coordinates, // and if it is a floor - move the player there. pub fn move_player(&mut self, delta_x: i32, delta_y: i32) { let current_position = idx_xy(self.player_position); let new_position = (current_position.0 + delta_x, current_position.1 + delta_y); let new_idx = xy_idx(new_position.0, new_position.1); if self.map[new_idx] == TileType::Floor { self.player_position = new_idx; self.visited[new_idx] = true; } } } // Implement the game loop impl GameState for State { fn tick(&mut self, ctx: &mut BTerm) { // New: handle keyboard inputs. match ctx.key { None => {} // Nothing happened Some(key) => { // A key is pressed or held match key { // We're matching a key code from GLFW (the GL library underlying BTerm), // and applying movement via the move_player function. // Numpad VirtualKeyCode::Numpad8 => self.move_player(0, -1), VirtualKeyCode::Numpad4 => self.move_player(-1, 0), VirtualKeyCode::Numpad6 => self.move_player(1, 0), VirtualKeyCode::Numpad2 => self.move_player(0, 1), // Numpad diagonals VirtualKeyCode::Numpad7 => self.move_player(-1, -1), VirtualKeyCode::Numpad9 => self.move_player(1, -1), VirtualKeyCode::Numpad1 => self.move_player(-1, 1), VirtualKeyCode::Numpad3 => self.move_player(1, 1), // Cursors VirtualKeyCode::Up => self.move_player(0, -1), VirtualKeyCode::Down => self.move_player(0, 1), VirtualKeyCode::Left => self.move_player(-1, 0), VirtualKeyCode::Right => self.move_player(1, 0), _ => {} // Ignore all the other possibilities } } } // Clear the screen ctx.cls(); // Iterate the map array, incrementing coordinates as we go. let mut y = 0; let mut x = 0; for (idx, tile) in self.map.iter().enumerate() { // Render a tile depending upon the tile type match tile { TileType::Floor => { ctx.print_color( x, y, RGB::from_f32(0.5, 0.5, 0.5), RGB::from_f32(0., 0., 0.), ".", ); } TileType::Wall => { ctx.print_color( x, y, RGB::from_f32(0.0, 1.0, 0.0), RGB::from_f32(0., 0., 0.), "#", ); } } if self.visited[idx] { ctx.set_bg(idx as i32 % 80, idx as i32 / 80, RGB::named(NAVY)); } // Move the coordinates x += 1; if x > 79 { x = 0; y += 1; } } // Render the player @ symbol let ppos = idx_xy(self.player_position); ctx.print_color( ppos.0, ppos.1, RGB::from_f32(1.0, 1.0, 0.0), RGB::from_f32(0., 0., 0.), "@", ); } } fn main() -> BError { let context = BTermBuilder::simple80x50() .with_title("Bracket Terminal Example - Walking") .build()?; let gs = State::new(); main_loop(context, gs) }