diff --git a/src/main.rs b/src/main.rs index 3ddc738..74ec58d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ -use std::io::Result; +use std::cmp::min; +use std::io::{Result, Stdout}; use std::io::stdout; use std::string::ToString; use std::time::Instant; @@ -48,229 +49,250 @@ struct CliOptions { fn main() -> Result<()> { let cli_options = CliOptions::parse(); - - let player_stats = PlayerStats::create_random(); - let mut game = Game::new(Player::new(realname().as_str(), player_stats)); - stdout().execute(EnterAlternateScreen)?; enable_raw_mode()?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; terminal.clear()?; if cli_options.manual { - loop { - let _ = terminal.draw(|frame| { - let mut area = frame.size(); - let w = area.width / 2; - let h = area.height / 2; - area.x += w - 20; - area.y += h - 10; - area.width = 40; - area.height = 20; - let block = Block::default() - .title(Title::from(" Manual ").alignment(Alignment::Center).position(Position::Top)) - .title(Title::from("Press `q` to quit!").position(Position::Bottom)) - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::White)) - .border_type(BorderType::Rounded) - .style(Style::default().bg(Color::Black)); - let mut text = "To control your character (depicted by the 8) you can use the cursor keys. \n\n\ - To interact simply walk into the entity to interact with. Possible entities are creates \ - (depicted by C), potions (depicted by P) or enemies.".to_string(); - let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); - frame.render_widget(paragraph, area); - }); - if event::poll(std::time::Duration::from_millis(16))? { - if let event::Event::Key(key) = event::read()? { - if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { - break; - } - } - } - } + show_manual(&mut terminal); } else { - let start_time = Instant::now(); - let mut ticks = 0; - loop { - terminal.draw(|frame| { - let mut area = frame.size(); - frame.render_widget(Block::default().style(Style::default().bg(Color::Green)), area); - - // don't draw stuff except an info box if the terminal is too small (less than 80x25) - // to prevent the read drawing code from crashing the game. - if area.width < 80 || area.height < 25 { - let block = Block::default() - .title("Info") - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::White)) - .border_type(BorderType::Rounded) - .style(Style::default().bg(Color::Black)); - let paragraph = Paragraph::new("Terminal needs to be at leas 80x25!") - .block(block) - .wrap(Wrap { trim: true }); - frame.render_widget(paragraph, area); - return; - } - - if area.width > 80 { - area.x = (area.width - 80) / 2; - area.width = 80; - } - if area.height > 25 { - area.y = (area.height - 25) / 2; - area.height = 25; - } - - let map_area = Rect { - x: area.x, - y: area.y, - width: level::LEVEL_WIDTH as u16, - height: level::LEVEL_HEIGHT as u16, - }; - frame.render_stateful_widget(LevelWidget::new(game.uses_fog_of_war()), map_area, &mut game); - - let stats_area = Rect { - x: area.x + 50, - y: area.y, - width: 30, - height: 15, - }; - let block = Block::default() - .title( - Title::from( - format!(" {} ", game.get_player().get_name())) - .alignment(Alignment::Center) - .position(Position::Top) - ) - .borders(Borders::TOP) - .border_style(Style::default().fg(Color::White)) - .border_type(BorderType::Rounded) - .style(Style::default().bg(Color::Blue)); - frame.render_widget( - Paragraph::new(format!("Health: {}/{}\nExp: {}\nGold: {}\nLevel: {}", - game.get_player().get_life(), - game.get_player().get_max_life(), - game.get_player().get_experience(), - game.get_player().get_gold(), - game.get_player().get_immutable_position().get_level())) - .block(block).wrap(Wrap { trim: true }), - stats_area, - ); - let messages_area = Rect { - x: area.x + 50, - y: area.y + 15, - width: 30, - height: 10, - }; - // Display the latest messages from the game to the user - let block = Block::default() - .title(Title::from(" messages ").alignment(Alignment::Center).position(Position::Top)) - .borders(Borders::TOP) - .border_style(Style::default().fg(Color::White)) - .border_type(BorderType::Rounded) - .style(Style::default().bg(Color::Blue)); - - let paragraph1 = if game.messages.is_empty() { "".to_string() } else { format!("> {}", game.messages.join("\n> ")) }; - frame.render_widget( - Paragraph::new(paragraph1).block(block).wrap(Wrap { trim: true }), - messages_area, - ); - })?; - let mut player_moved = false; - if event::poll(std::time::Duration::from_millis(FRAME_LENGTH))? { - if let event::Event::Key(key) = event::read()? { - if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('v') { - game.messages.insert(0, format!("You are playing version '{}'.", env!("GIT_HASH")).to_string()); - } - // disabling the d key (disable fog of war) in release builds! - #[cfg(debug_assertions)] - if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('d') { - game.messages.insert(0, "toggle fog of war!".to_string()); - game.toggle_fog_of_war(); - } - - if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { - break; - } - if key.kind == KeyEventKind::Press { - let new_pos = match key.code { - KeyCode::Left => { - player_moved = true; - game.move_player(-1, 0) - } - KeyCode::Right => { - player_moved = true; - game.move_player(1, 0) - } - KeyCode::Up => { - player_moved = true; - game.move_player(0, -1) - } - KeyCode::Down => { - player_moved = true; - game.move_player(0, 1) - } - _ => { (0, 0) } - }; - if !game.player_fights_monster() { - // player attacked monster but did not kill it - game.move_player(new_pos.0, new_pos.1); - } - game.player_collects_artifact(); - } - } - } - game.update_level(ticks, player_moved); - if game.get_game_state() != GameState::RUNNING { - break; - } - ticks += 1; - } - let playtime = start_time.elapsed(); - loop { - let _ = terminal.draw(|frame| { - let mut area = frame.size(); - let w = area.width / 2; - let h = area.height / 2; - area.x += w - 20; - area.y += h - 10; - area.width = 40; - area.height = 20; - let block = Block::default() - .title(Title::from(" Game ended ").alignment(Alignment::Center).position(Position::Top)) - .title(Title::from("Press `q` to quit!").position(Position::Bottom)) - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::White)) - .border_type(BorderType::Rounded) - .style(Style::default().bg(Color::Black)); - let mut text = match game.get_game_state() { - GameState::RUNNING => { - "Quitting is for cowards! You'll better try again!".to_string() - } - GameState::LOST => { - format!("Sorry, you died in the dungeon. Better luck next time!\nLast message:\n{}", game.messages[0]).to_string() - } - GameState::WON => { - "Congratulation! You mastered your way through the dungeon and won the game.".to_string() - } - }; - text += format!("\n\nYou gained {} experience.", game.get_player().get_experience()).as_str(); - text += format!("\nYou collected {} gold.", game.get_player().get_gold()).as_str(); - text += format!("\nYou played {} seconds.", playtime.as_secs()).as_str(); - let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); - frame.render_widget(paragraph, area); - }); - if event::poll(std::time::Duration::from_millis(16))? { - if let event::Event::Key(key) = event::read()? { - if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { - break; - } - } - } - } + play_game(&mut terminal)?; } stdout().execute(LeaveAlternateScreen)?; disable_raw_mode()?; Ok(()) } + +/// play the game ;) +fn play_game(terminal: &mut Terminal>) -> Result<()> { + let player_stats = PlayerStats::create_random(); + let mut game = Game::new(Player::new(realname().as_str(), player_stats)); + let start_time = Instant::now(); + let mut ticks = 0; + loop { + terminal.draw(|frame| { + let mut area = frame.size(); + frame.render_widget(Block::default().style(Style::default().bg(Color::Green)), area); + + // don't draw stuff except an info box if the terminal is too small (less than 80x25) + // to prevent the read drawing code from crashing the game. + if area.width < 80 || area.height < 25 { + let block = Block::default() + .title("Info") + .borders(Borders::ALL) + .border_style(Style::default().fg(Color::White)) + .border_type(BorderType::Rounded) + .style(Style::default().bg(Color::Black)); + let paragraph = Paragraph::new("Terminal needs to be at leas 80x25!") + .block(block) + .wrap(Wrap { trim: true }); + frame.render_widget(paragraph, area); + return; + } + + if area.width > 80 { + area.x = (area.width - 80) / 2; + area.width = 80; + } + if area.height > 25 { + area.y = (area.height - 25) / 2; + area.height = 25; + } + + let map_area = Rect { + x: area.x, + y: area.y, + width: level::LEVEL_WIDTH as u16, + height: level::LEVEL_HEIGHT as u16, + }; + frame.render_stateful_widget(LevelWidget::new(game.uses_fog_of_war()), map_area, &mut game); + + let stats_area = Rect { + x: area.x + 50, + y: area.y, + width: 30, + height: 15, + }; + let block = Block::default() + .title( + Title::from( + format!(" {} ", game.get_player().get_name())) + .alignment(Alignment::Center) + .position(Position::Top) + ) + .borders(Borders::TOP) + .border_style(Style::default().fg(Color::White)) + .border_type(BorderType::Rounded) + .style(Style::default().bg(Color::Blue)); + frame.render_widget( + Paragraph::new(format!("Health: {}/{}\nExp: {}\nGold: {}\nLevel: {}", + game.get_player().get_life(), + game.get_player().get_max_life(), + game.get_player().get_experience(), + game.get_player().get_gold(), + game.get_player().get_immutable_position().get_level())) + .block(block).wrap(Wrap { trim: true }), + stats_area, + ); + let messages_area = Rect { + x: area.x + 50, + y: area.y + 15, + width: 30, + height: 10, + }; + // Display the latest messages from the game to the user + let block = Block::default() + .title(Title::from(" messages ").alignment(Alignment::Center).position(Position::Top)) + .borders(Borders::TOP) + .border_style(Style::default().fg(Color::White)) + .border_type(BorderType::Rounded) + .style(Style::default().bg(Color::Blue)); + + let paragraph1 = if game.messages.is_empty() { "".to_string() } else { format!("> {}", game.messages.join("\n> ")) }; + frame.render_widget( + Paragraph::new(paragraph1).block(block).wrap(Wrap { trim: true }), + messages_area, + ); + })?; + let mut player_moved = false; + if event::poll(std::time::Duration::from_millis(FRAME_LENGTH))? { + if let event::Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('v') { + game.messages.insert(0, format!("You are playing version '{}'.", env!("GIT_HASH")).to_string()); + } + // disabling the d key (disable fog of war) in release builds! + #[cfg(debug_assertions)] + if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('d') { + game.messages.insert(0, "toggle fog of war!".to_string()); + game.toggle_fog_of_war(); + } + + if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { + break; + } + if key.kind == KeyEventKind::Press { + let new_pos = match key.code { + KeyCode::Left => { + player_moved = true; + game.move_player(-1, 0) + } + KeyCode::Right => { + player_moved = true; + game.move_player(1, 0) + } + KeyCode::Up => { + player_moved = true; + game.move_player(0, -1) + } + KeyCode::Down => { + player_moved = true; + game.move_player(0, 1) + } + _ => { (0, 0) } + }; + if !game.player_fights_monster() { + // player attacked monster but did not kill it + game.move_player(new_pos.0, new_pos.1); + } + game.player_collects_artifact(); + } + } + } + game.update_level(ticks, player_moved); + if game.get_game_state() != GameState::RUNNING { + break; + } + ticks += 1; + } + let playtime = start_time.elapsed(); + loop { + let _ = terminal.draw(|frame| { + let mut area = frame.size(); + let w = area.width / 2; + let h = area.height / 2; + area.x += w - 20; + area.y += h - 10; + area.width = 40; + area.height = 20; + let block = Block::default() + .title(Title::from(" Game ended ").alignment(Alignment::Center).position(Position::Top)) + .title(Title::from("Press `q` to quit!").position(Position::Bottom)) + .borders(Borders::ALL) + .border_style(Style::default().fg(Color::White)) + .border_type(BorderType::Rounded) + .style(Style::default().bg(Color::Black)); + let mut text = match game.get_game_state() { + GameState::RUNNING => { + "Quitting is for cowards! You'll better try again!".to_string() + } + GameState::LOST => { + format!("Sorry, you died in the dungeon. Better luck next time!\nLast message:\n{}", game.messages[0]).to_string() + } + GameState::WON => { + "Congratulation! You mastered your way through the dungeon and won the game.".to_string() + } + }; + text += format!("\n\nYou gained {} experience.", game.get_player().get_experience()).as_str(); + text += format!("\nYou collected {} gold.", game.get_player().get_gold()).as_str(); + text += format!("\nYou played {} seconds.", playtime.as_secs()).as_str(); + let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); + frame.render_widget(paragraph, area); + }); + if event::poll(std::time::Duration::from_millis(16))? { + if let event::Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { + break; + } + } + } + } + Ok(()) +} + +/// this function shows some manual text +fn show_manual(terminal: &mut Terminal>) { + let mut page = 0; + let pages = [ + "Background story\n\n\ + You are a traveller and on your journey.... + ".to_string(), + "To control your character (depicted by the 8) you can use the cursor keys (←, ↑, →, ↓). \n\n\ + To interact simply walk into the entity to interact with. Possible entities are creates \ + (depicted by C), potions (depicted by P) or enemies.".to_string(), + ]; + loop { + let _ = terminal.draw(|frame| { + let mut area = frame.size(); + let w = area.width / 2; + let h = area.height / 2; + area.x += w - 20; + area.y += h - 10; + area.width = 40; + area.height = 20; + let block = Block::default() + .title(Title::from(format!(" Manual ({}/{})", page + 1, pages.len())).alignment(Alignment::Center).position(Position::Top)) + .title(Title::from("`q` to quit, `↑` and `↓` to scroll.").position(Position::Bottom)) + .borders(Borders::ALL) + .border_style(Style::default().fg(Color::White)) + .border_type(BorderType::Rounded) + .style(Style::default().bg(Color::Black)); + let paragraph = Paragraph::new(pages[page].as_str()).block(block).wrap(Wrap { trim: true }); + frame.render_widget(paragraph, area); + }); + if event::poll(std::time::Duration::from_millis(16)).unwrap_or(false) { + if let event::Event::Key(key) = event::read().unwrap() { + if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { + break; + } + if key.kind == KeyEventKind::Press && key.code == KeyCode::Up { + page = page.saturating_sub(1); + } + if key.kind == KeyEventKind::Press && key.code == KeyCode::Down { + page = min(pages.len() - 1, page + 1); + } + } + } + } +}