diff --git a/src/main.rs b/src/main.rs index e264a72..f725d40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,8 @@ const MAX_ROOMS: i32 = 30; const FOV_ALGO: FovAlgorithm = FovAlgorithm::Basic; const FOV_LIGHT_WALLS: bool = true; const TORCH_RADIUS: i32 = 10; +const MAX_ROOM_MONSTERS: i32 = 3; +const PLAYER: usize = 0; struct Tcod { root: Root, @@ -49,18 +51,21 @@ struct Object { y: i32, char: char, color: Color, + name: String, + blocks: bool, + alive: bool, } impl Object { - pub fn new(x: i32, y: i32, char: char, color: Color) -> Self { - Object { x, y, char, color } - } - - // Move by given amount, if the destination is not blocked - pub fn move_by(&mut self, dx: i32, dy: i32, game: &Game) { - if !game.map[(self.x + dx) as usize][(self.y + dy) as usize].blocked { - self.x += dx; - self.y += dy; + pub fn new(x: i32, y: i32, char: char, name: &str, color: Color, blocks: bool) -> Self { + Object { + x: x, + y: y, + char: char, + color: color, + name: name.into(), + blocks: blocks, + alive: false, } } @@ -69,6 +74,15 @@ impl Object { con.set_default_foreground(self.color); con.put_char(self.x, self.y, self.char, BackgroundFlag::None); } + + pub fn pos(&self) -> (i32, i32) { + (self.x, self.y) + } + + pub fn set_pos(&mut self, x: i32, y: i32) { + self.x = x; + self.y = y; + } } #[derive(Clone, Copy, Debug)] @@ -132,34 +146,60 @@ impl Rect { } } -fn handle_keys(tcod: &mut Tcod, game: &Game, player: &mut Object) -> bool { +#[derive(Clone, Copy, Debug, PartialEq)] +enum PlayerAction { + TookTurn, + DidntTakeTurn, + Exit, +} + +fn handle_keys(tcod: &mut Tcod, game: &Game, objects: &mut Vec) -> PlayerAction { use tcod::input::Key; use tcod::input::KeyCode::*; + use PlayerAction::*; let key = tcod.root.wait_for_keypress(true); - match key { - Key { - code: Enter, - alt: true, - .. - } => { + let player_alive = objects[PLAYER].alive; + + match (key, key.text(), player_alive) { + ( + Key { + code: Enter, + alt: true, + .. + }, + _, + _, + ) => { // Alt+Enter: toggle fullscreen let fullscreen = tcod.root.is_fullscreen(); tcod.root.set_fullscreen(!fullscreen); + DidntTakeTurn } - Key { code: Escape, .. } => return true, // exit game + (Key { code: Escape, .. }, _, _) => Exit, // exit game - Key { code: Up, .. } => player.move_by(0, -1, game), - Key { code: Down, .. } => player.move_by(0, 1, game), - Key { code: Left, .. } => player.move_by(-1, 0, game), - Key { code: Right, .. } => player.move_by(1, 0, game), + (Key { code: Up, .. }, _, true) => { + player_move_or_attack(0, -1, game, objects); + TookTurn + } + (Key { code: Down, .. }, _, true) => { + player_move_or_attack(0, 1, game, objects); + TookTurn + } + (Key { code: Left, .. }, _, true) => { + player_move_or_attack(-1, 0, game, objects); + TookTurn + } + (Key { code: Right, .. }, _, true) => { + player_move_or_attack(1, 0, game, objects); + TookTurn + } - _ => {} + _ => DidntTakeTurn, } - false } -fn make_map(player: &mut Object) -> Map { +fn make_map(objects: &mut Vec) -> Map { // Fill the map with unblocked tiles let mut map = vec![vec![Tile::wall(); MAP_HEIGHT as usize]; MAP_WIDTH as usize]; @@ -178,11 +218,11 @@ fn make_map(player: &mut Object) -> Map { if !failed { create_room(new_room, &mut map); + place_objects(new_room, &map, objects); let (new_x, new_y) = new_room.center(); if rooms.is_empty() { - player.x = new_x; - player.y = new_y; + objects[PLAYER].set_pos(new_x, new_y); } else { let (prev_x, prev_y) = rooms[rooms.len() - 1].center(); @@ -225,9 +265,71 @@ fn create_v_tunnel(y1: i32, y2: i32, x: i32, map: &mut Map) { } } +fn place_objects(room: Rect, map: &Map, objects: &mut Vec) { + let num_monsters = rand::thread_rng().gen_range(0, MAX_ROOM_MONSTERS + 1); + + for _ in 0..num_monsters { + let x = rand::thread_rng().gen_range(room.x1 + 1, room.x2); + let y = rand::thread_rng().gen_range(room.y1 + 1, room.y2); + + if !is_blocked(x, y, map, objects) { + let mut monster = if rand::random::() < 0.8 { + // 80% chance of spawning an orc + Object::new(x, y, 'o', "orc", DESATURATED_GREEN, true) + } else { + // 20% chance of spawning a troll + Object::new(x, y, 'T', "troll", DARKER_GREEN, true) + }; + + monster.alive = true; + objects.push(monster); + } + } +} + +fn is_blocked(x: i32, y: i32, map: &Map, objects: &[Object]) -> bool { + if map[x as usize][y as usize].blocked { + return true; + } + + objects + .iter() + .any(|object| object.blocks && object.pos() == (x, y)) +} + +// Move by given amount, if the destination is not blocked +fn move_by(id: usize, dx: i32, dy: i32, map: &Map, objects: &mut [Object]) { + let (x, y) = objects[id].pos(); + if !is_blocked(x + dx, y + dy, map, objects) { + objects[id].set_pos(x + dx, y + dy); + } +} + +fn player_move_or_attack(dx: i32, dy: i32, game: &Game, objects: &mut [Object]) { + // The coordinates the player is moving to / attacking + let x = objects[PLAYER].x + dx; + let y = objects[PLAYER].y + dy; + + // Try to find an attackable object there + let target_id = objects.iter().position(|object| object.pos() == (x, y)); + + // Attack target if found, move otherwise + match target_id { + Some(target_id) => { + println!( + "The {} laughs at your puny efforts to attack him!", + objects[target_id].name + ); + } + None => { + move_by(PLAYER, dx, dy, &game.map, objects); + } + } +} + fn render_all(tcod: &mut Tcod, game: &mut Game, objects: &[Object], fov_recompute: bool) { if fov_recompute { - let player = &objects[0]; + let player = &objects[PLAYER]; tcod .fov .compute_fov(player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO); @@ -294,17 +396,15 @@ fn main() { }; // Create player object - let player = Object::new(0, 0, '@', WHITE); - - // // Create an NPC - let npc = Object::new(SCREEN_WIDTH / 2 - 5, SCREEN_HEIGHT / 2, '@', YELLOW); + let mut player = Object::new(0, 0, '@', "player", WHITE, true); + player.alive = true; // List of objects in the world - let mut objects = [player, npc]; + let mut objects = vec![player]; let mut game = Game { // Generate map - map: make_map(&mut objects[0]), + map: make_map(&mut objects), }; // Populate FOV map, according to generated map @@ -325,15 +425,24 @@ fn main() { while !tcod.root.window_closed() { tcod.con.clear(); - let fov_recompute = previous_player_position != (objects[0].x, objects[0].y); + let fov_recompute = previous_player_position != (objects[PLAYER].pos()); render_all(&mut tcod, &mut game, &objects, fov_recompute); tcod.root.flush(); - let player = &mut objects[0]; - previous_player_position = (player.x, player.y); - let exit = handle_keys(&mut tcod, &game, player); - if exit { + previous_player_position = objects[PLAYER].pos(); + let player_action = handle_keys(&mut tcod, &game, &mut objects); + if player_action == PlayerAction::Exit { break; } + + // Let monsters take their turn + if objects[PLAYER].alive && player_action != PlayerAction::DidntTakeTurn { + for object in &objects { + // ONly if object is not player + if (object as *const _) != (&objects[PLAYER] as *const _) { + println!("The {} growls!", object.name); + } + } + } } }