<commit_before>//! Application utilities

use std;
use std::error::Error;
use clap;
use simplelog;

/// Derived argument parser
///
/// Usage:
/// ```text
/// use clap::Parser;
/// let app = rs_utils::App::parse();
/// ```
#[derive(Debug, clap::Parser)]
#[clap(about = "rs-utils app", version)]
pub struct App {
  #[clap(flatten)]
  pub args : Args
}

/// Derived clap arguments
#[derive(Debug, clap::Args)]
pub struct Args {
  #[clap(short = 'l', help = "Global log level filter")]
  pub log_level     : Option <simplelog::LevelFilter>,
  #[clap(short = 'm',
    name = "MODULE=LEVEL",
    value_parser = parse_key_val::<String, simplelog::LevelFilter>,
    help = "Module log filters")]
  pub module_filters : Vec <(String, simplelog::LevelFilter)>
}

/// Initialize `simplelog` terminal logger to stdout with the given log level.
///
/// This sets the "target level" to `Error` (show module paths for all levels)
/// and sets the "thread level" to `Off` (don't show thread numbers for any
/// levels).
pub fn init_simple_termlogger (log_level : simplelog::LevelFilter) {
  simplelog::TermLogger::init (
    log_level,
    base_simplelog_config_builder().build(),
    simplelog::TerminalMode::Stdout,
    simplelog::ColorChoice::Auto
  ).unwrap();
}

/// Initialize a `simplelog` terminal logger to stdout with a given global log
/// level and list of module filters.
///
/// This sets the "target level" to `Error` (show module paths for all levels)
/// and sets the "thread level" to `Off` (don't show thread numbers for any
/// levels).
pub fn init_combined_termlogger (
  global_log_level  : simplelog::LevelFilter,
  module_log_levels : Vec <(String, simplelog::LevelFilter)>
) {
  let global_config = {
    let mut builder = base_simplelog_config_builder();
    for (module, _) in module_log_levels.iter().cloned() {
      builder.add_filter_ignore (module);
    }
    builder.build()
  };
  let mut loggers : Vec <Box <dyn simplelog::SharedLogger>> = vec![
    simplelog::TermLogger::new (
      global_log_level,
      global_config,
      simplelog::TerminalMode::Stdout,
      simplelog::ColorChoice::Auto
    )
  ];
  for (module, level_filter) in module_log_levels {
    let mut builder = base_simplelog_config_builder();
    builder.add_filter_allow (module);
    loggers.push (simplelog::TermLogger::new (
      level_filter,
      builder.build(),
      simplelog::TerminalMode::Stdout,
      simplelog::ColorChoice::Auto
    ));
  }
  simplelog::CombinedLogger::init (loggers).unwrap();
}

/// Gets the `file_name()` of the `std::env::current_exe()`
#[inline]
pub fn exe_name() -> std::ffi::OsString {
  std::env::current_exe().unwrap().file_name().unwrap().to_owned()
}

//
//  private
//

/// `simplelog` config builder with "target level" to `Error` (show module paths
/// for all levels) and sets the "thread level" to `Off` (don't show thread
/// numbers for any levels).
fn base_simplelog_config_builder() -> simplelog::ConfigBuilder {
  let mut builder = simplelog::ConfigBuilder::new();
  builder.set_target_level (simplelog::LevelFilter::Error); // module path
  builder.set_thread_level (simplelog::LevelFilter::Off);   // no thread numbers
  builder
}

fn parse_key_val <T, U> (s : &str)
  -> Result <(T, U), Box <dyn Error + Send + Sync + 'static>>
where
  T      : std::str::FromStr,
  T::Err : Error + Send + Sync + 'static,
  U      : std::str::FromStr,
  U::Err : Error + Send + Sync + 'static,
{
  let pos = s.find ('=')
    .ok_or_else (|| format!("invalid KEY=value: no `=` found in `{}`", s))?;
  Ok ((s[..pos].parse()?, s[pos + 1..].parse()?))
}

////////////////////////////////////////////////////////////////////////////////
//  impls
////////////////////////////////////////////////////////////////////////////////

impl Args {
  pub fn init_termlogger (&self) {
  }
}
<commit_msg>add writelogger init functions<commit_after>//! Application utilities

use std;
use std::error::Error;
use clap;
use simplelog;

/// Derived argument parser
///
/// Usage:
/// ```text
/// use clap::Parser;
/// let app = rs_utils::App::parse();
/// ```
#[derive(Debug, clap::Parser)]
#[clap(about = "rs-utils app", version)]
pub struct App {
  #[clap(flatten)]
  pub args : Args
}

/// Derived clap arguments
#[derive(Debug, clap::Args)]
pub struct Args {
  #[clap(short = 'l', help = "Global log level filter")]
  pub log_level     : Option <simplelog::LevelFilter>,
  #[clap(short = 'm',
    name = "MODULE=LEVEL",
    value_parser = parse_key_val::<String, simplelog::LevelFilter>,
    help = "Module log filters")]
  pub module_filters : Vec <(String, simplelog::LevelFilter)>
}

/// Initialize `simplelog` terminal logger to stdout with the given log level.
///
/// This sets the "target level" to `Error` (show module paths for all levels)
/// and sets the "thread level" to `Off` (don't show thread numbers for any
/// levels).
pub fn init_simple_termlogger (log_level : simplelog::LevelFilter) {
  simplelog::TermLogger::init (
    log_level,
    base_simplelog_config_builder().build(),
    simplelog::TerminalMode::Stdout,
    simplelog::ColorChoice::Auto
  ).unwrap();
}

/// Initialize `simplelog` write logger to `Write` object with the given log
/// level.
///
/// This sets the "target level" to `Error` (show module paths for all levels)
/// and sets the "thread level" to `Off` (don't show thread numbers for any
/// levels).
pub fn init_simple_writelogger <W> (
  writeable : W,
  log_level : simplelog::LevelFilter
) where
  W : std::io::Write + Send + 'static
{
  simplelog::WriteLogger::init (
    log_level,
    base_simplelog_config_builder().build(),
    writeable
  ).unwrap();
}

/// Initialize a `simplelog` terminal logger to stdout with a given global log
/// level and list of module filters.
///
/// This sets the "target level" to `Error` (show module paths for all levels)
/// and sets the "thread level" to `Off` (don't show thread numbers for any
/// levels).
pub fn init_combined_termlogger (
  global_log_level  : simplelog::LevelFilter,
  module_log_levels : Vec <(String, simplelog::LevelFilter)>
) {
  let global_config = {
    let mut builder = base_simplelog_config_builder();
    for (module, _) in module_log_levels.iter().cloned() {
      builder.add_filter_ignore (module);
    }
    builder.build()
  };
  let mut loggers : Vec <Box <dyn simplelog::SharedLogger>> = vec![
    simplelog::TermLogger::new (
      global_log_level,
      global_config,
      simplelog::TerminalMode::Stdout,
      simplelog::ColorChoice::Auto
    )
  ];
  for (module, level_filter) in module_log_levels {
    let mut builder = base_simplelog_config_builder();
    builder.add_filter_allow (module);
    loggers.push (simplelog::TermLogger::new (
      level_filter,
      builder.build(),
      simplelog::TerminalMode::Stdout,
      simplelog::ColorChoice::Auto
    ));
  }
  simplelog::CombinedLogger::init (loggers).unwrap();
}

/// Initialize a `simplelog` write logger with a given global log level and list
/// of module filters.
///
/// This sets the "target level" to `Error` (show module paths for all levels)
/// and sets the "thread level" to `Off` (don't show thread numbers for any
/// levels).
pub fn init_combined_writelogger <W> (
  writeable         : W,
  global_log_level  : simplelog::LevelFilter,
  module_log_levels : Vec <(String, simplelog::LevelFilter)>,
) where
  W : std::io::Write + Send + Clone + 'static
{
  let global_config = {
    let mut builder = base_simplelog_config_builder();
    for (module, _) in module_log_levels.iter().cloned() {
      builder.add_filter_ignore (module);
    }
    builder.build()
  };
  let mut loggers : Vec <Box <dyn simplelog::SharedLogger>> = vec![
    simplelog::WriteLogger::new (
      global_log_level, global_config, writeable.clone()
    )
  ];
  for (module, level_filter) in module_log_levels {
    let mut builder = base_simplelog_config_builder();
    builder.add_filter_allow (module);
    loggers.push (simplelog::WriteLogger::new (
      level_filter, builder.clone().build(), writeable.clone()
    ));
  }
  simplelog::CombinedLogger::init (loggers).unwrap();
}

/// Gets the `file_name()` of the `std::env::current_exe()`
#[inline]
pub fn exe_name() -> std::ffi::OsString {
  std::env::current_exe().unwrap().file_name().unwrap().to_owned()
}

//
//  private
//

/// `simplelog` config builder with "target level" to `Error` (show module paths
/// for all levels) and sets the "thread level" to `Off` (don't show thread
/// numbers for any levels).
fn base_simplelog_config_builder() -> simplelog::ConfigBuilder {
  let mut builder = simplelog::ConfigBuilder::new();
  builder.set_target_level (simplelog::LevelFilter::Error); // module path
  builder.set_thread_level (simplelog::LevelFilter::Off);   // no thread numbers
  builder
}

fn parse_key_val <T, U> (s : &str)
  -> Result <(T, U), Box <dyn Error + Send + Sync + 'static>>
where
  T      : std::str::FromStr,
  T::Err : Error + Send + Sync + 'static,
  U      : std::str::FromStr,
  U::Err : Error + Send + Sync + 'static,
{
  let pos = s.find ('=')
    .ok_or_else (|| format!("invalid KEY=value: no `=` found in `{}`", s))?;
  Ok ((s[..pos].parse()?, s[pos + 1..].parse()?))
}

////////////////////////////////////////////////////////////////////////////////
//  impls
////////////////////////////////////////////////////////////////////////////////

impl Args {
  pub fn init_termlogger (&self) {
  }
}
<|endoftext|>


<commit_before>use piston_window::UpdateArgs;
use rand::{Rng, thread_rng};

use player::{Player, Ship};
use settings::Settings;

pub struct App {
    pub settings: Settings,
    pub players: [Player; 2],
    pub state: u8,
    pub turn: u8,
    pub turn_active: bool,
    pub game_over: bool,
    pub winner: Option<u8>,
    pub turn_end_timer: f64,
    pub cpu_turn_timer: f64,
    pub grid_area: [u32; 4],
    pub window_size: [u32; 2],
    mouse_cursor: [f64; 2],
    pub ship_temp_pos: Vec<[u8; 2]>,
    ship_temp_dir: u8,
}

impl App {
    pub fn new(space_size: u32, width: u8, height: u8) -> App {
        let grid_area = [
            space_size,
            space_size * 3,
            width as u32 * space_size,
            height as u32 * space_size
        ];

        let window_size = [
            grid_area[2] + space_size * 2,
            grid_area[3] + space_size * 4
        ];

        App {
            settings: Settings {
                space_size: space_size,
                width: width,
                height: height,
            },
            players: [Player::new(false), Player::new(true)],
            state: 0,
            turn: 0,
            turn_active: true,
            game_over: false,
            winner: None,
            turn_end_timer: 0.0,
            cpu_turn_timer: 0.0,
            grid_area: grid_area,
            window_size: window_size,
            mouse_cursor: [0.0; 2],
            ship_temp_pos: vec![[0, 0], [1, 0]],
            ship_temp_dir: 0,
        }
    }

    pub fn update(&mut self, u: &UpdateArgs) {
        if self.state == 0 {
            if self.players[self.turn as usize].is_cpu {
                self.players[self.turn as usize].cpu_place_ships();
            }

            if self.players[self.turn as usize].ships.len() == 4 {
                self.switch_turn();
            }

            // All ships have been placed; start the game.
            if self.players[0].ships.len() == 4 && self.players[1].ships.len() == 4 {
                self.state = 1;
            }
        } else if self.state == 1 || self.state == 2 {
            if !self.turn_active && !self.game_over {

                // Continue/end the end-of-turn delay.
                if self.turn_end_timer < 1.5 {
                    self.turn_end_timer += u.dt;
                } else {
                    if self.state != 2 {
                        self.switch_turn();
                    } else {
                        self.game_over = true;
                        self.winner = Some(self.turn);
                    }
                }
            }

            // Continue/end the delay when CPU players take their turn.
            if self.turn_active && self.players[self.turn as usize].is_cpu {
                self.cpu_turn_timer += u.dt;
                if self.cpu_turn_timer >= 1.0 {
                    self.cpu_turn_timer = 0.0;
                    let cpu_space = self.cpu_select_space();
                    self.select_space(cpu_space[0], cpu_space[1]);
                }
            }
        }
    }

    /// Selects a space, and performs related checks on the status of ships if
    /// there's a hit.
    fn select_space(&mut self, x: u8, y: u8) {
        let ref mut opponent = self.players[self.not_turn()];
        let mut space_state = 1;
        let mut hit_ship = None;
        for (i, ship) in opponent.ships.iter().enumerate() {
            if ship.position.contains(&[x, y]) {
                space_state = 2;
                hit_ship = Some(i);
            }
        }

        let space = opponent.spaces.iter().position(|space| space.position == [x, y]).unwrap();
        opponent.spaces[space].state = space_state;

        if space_state == 2 {
            // Check if this ship has sunk.
            let hit_ship = hit_ship.unwrap();
            let mut ship_state = false;
            for ship_pos in &opponent.ships[hit_ship].position {
                if let Some(space) = opponent.spaces.iter().position(|space| &space.position == ship_pos) {
                    if opponent.spaces[space].state == 0 {
                        ship_state = true;
                    }
                }
            }

            if !ship_state {
                opponent.ships[hit_ship].state = ship_state;
            }

            // Check if any ships are left.
            let mut all_sunk = true;
            for ship in &opponent.ships {
                if ship.state {
                    all_sunk = false;
                    break;
                }
            }

            if all_sunk {
                self.state = 2;
            }
        }

        self.turn_active = false;
    }

    /// Uses RNG to select a space for CPU players.
    fn cpu_select_space(&mut self) -> [u8; 2] {
        let mut rng = thread_rng();
        let mut first_hit = None;
        let mut select = vec![];

        // Determines the order of priority of directions to check if there are
        // any hit spaces found.
        let mut directions: [[i32; 2]; 4] = [
            [-1, 0],
            [1, 0],
            [0, -1],
            [0, 1]
        ];
        rng.shuffle(&mut directions);

        let ref opponent = self.players[self.not_turn()];

        for space in &opponent.spaces {
            if space.state == 2 {

                // Get the hit ship.
                let mut ship: Option<&Ship> = None;
                for s in &opponent.ships {
                    if s.position.contains(&space.position) {
                        ship = Some(s);
                        break;
                    }
                }

                // Make sure the hit ship hasn't been sunk.
                if ship.unwrap().state {
                    if first_hit.is_none() {
                        first_hit = Some(space.position);
                    }

                    // Check if this space forms part of a line of hit spaces.
                    // If it does, and the space at the end hasn't been
                    // selected yet, it's a candidate for selection this turn.
                    for check in &directions {
                        let mut xc = space.position[0];
                        let mut yc = space.position[1];

                        while opponent.get_space_state((xc as i32 + check[0]) as u8, (yc as i32 + check[1]) as u8) == Some(2) {
                            xc = (xc as i32 + check[0]) as u8;
                            yc = (yc as i32 + check[1]) as u8;
                        }

                        if opponent.get_space_state((xc as i32 + check[0]) as u8, (yc as i32 + check[1]) as u8) == Some(0) && (xc != space.position[0] || yc != space.position[1]) {
                            select.push([
                                (xc as i32 + check[0]) as u8,
                                (yc as i32 + check[1]) as u8
                            ]);
                            break;
                        }
                    }
                }
            }
        }

        // If a hit space was found, but no hit spaces next to it, select a
        // non-selected space next to it.
        if first_hit.is_some() && select.len() == 0 {
            let first_hit = first_hit.unwrap();
            for check in &directions {
                if opponent.get_space_state((first_hit[0] as i32 + check[0]) as u8, (first_hit[1] as i32 + check[1]) as u8) == Some(0) {
                    select.push([
                        (first_hit[0] as i32 + check[0]) as u8,
                        (first_hit[1] as i32 + check[1]) as u8
                    ]);
                    break;
                }
            }
        }

        // If no spaces were selected to check, just check any available space.
        if select.len() == 0 {
            let mut x: u8 = rng.gen_range(0, self.settings.width);
            let mut y: u8 = rng.gen_range(0, self.settings.height);
            let mut space_state = opponent.get_space_state(x, y);

            while space_state != Some(0) {
                x = rng.gen_range(0, self.settings.width);
                y = rng.gen_range(0, self.settings.height);
                space_state = opponent.get_space_state(x, y);
            }

            select.push([x, y]);
        }

        // The way the potential selections are chosen, empty spaces to the
        // right or bottom of a line of hit spaces will always be chosen first,
        // so the list of selections should be shuffled.
        if select.len() > 1 {
            select.dedup();
            rng.shuffle(&mut select);
        }

        select[0]
    }

    /// Returns the player who is not currently taking their turn.
    pub fn not_turn(&self) -> usize {
        (self.turn + 1) as usize % 2
    }

    /// When a player's turn is finished, this sets the other player as active.
    fn switch_turn(&mut self) {
        self.turn = self.not_turn() as u8;
        self.turn_active = true;
        self.turn_end_timer = 0.0;
    }

    /// Processes all left mouse clicks according to the current program state.
    pub fn mouse_left_click(&mut self) {
        if let Some(grid_pos) = self.mouse_cursor_grid_position() {
            if self.state == 0 && self.turn_active && !self.players[self.turn as usize].is_cpu {

                // State 0: place ships.
                let mut ship = vec![];
                for pos in &self.ship_temp_pos {
                    ship.push(*pos);
                }

                if self.players[self.turn as usize].valid_ship_position(&ship) {
                    self.players[self.turn as usize].ships.push(Ship {
                        position: ship,
                        state: true,
                    });
                }
            } else if self.state == 1 && self.turn_active && !self.players[self.turn as usize].is_cpu {

                // State 1: select spaces on opponent's grid.
                if let Some(space) = self.players[self.not_turn()].spaces.iter().position(|space| space.position == grid_pos) {
                    if self.players[self.not_turn()].spaces[space].state == 0 {
                        self.select_space(grid_pos[0], grid_pos[1]);
                    }
                }
            }
        }
    }

    /// Processes all right mouse clicks according to the current program state.
    pub fn mouse_right_click(&mut self) {
        if self.state == 0 {

            // State 0: rotate ships.
            let mut new_ship_pos = vec![self.ship_temp_pos[0]];
            match self.ship_temp_dir {
                0 => {
                    self.ship_temp_dir = 1;
                    let length = self.players[self.turn as usize].ships.len() + 2;
                    for pos in 1..length {
                        new_ship_pos.push([self.ship_temp_pos[0][0], self.ship_temp_pos[0][1] + pos as u8]);
                    }
                },
                1 => {
                    self.ship_temp_dir = 0;
                    let length = self.players[self.turn as usize].ships.len() + 2;
                    for pos in 1..length {
                        new_ship_pos.push([self.ship_temp_pos[0][0] + pos as u8, self.ship_temp_pos[0][1]]);
                    }
                },
                _ => {}
            }

            self.ship_temp_pos = new_ship_pos;
        }
    }

    /// Records the last known mouse cursor position.
    pub fn mouse_cursor_movement(&mut self, c: &[f64; 2]) {
        self.mouse_cursor = *c;

        if let Some(grid_pos) = self.mouse_cursor_grid_position() {
            let ref mut player = self.players[self.turn as usize];

            // During ship placement, set the temporary ship position so it can
            // be drawn by the window renderer.
            if self.state == 0 {
                self.ship_temp_pos = player.get_ship_position(
                    grid_pos,
                    self.ship_temp_dir,
                    player.ships.len() as u8 + 2
                );
            }

            // State 1: set the player's grid cursor.
            if self.state == 1 {
                if self.turn_end_timer == 0.0 && !player.is_cpu {
                    player.grid_cursor = grid_pos;
                }
            }
        }
    }

    /// Returns the grid coordinates of the mouse cursor position.
    fn mouse_cursor_grid_position(&self) -> Option<[u8; 2]> {
        let position: Option<[u8; 2]>;
        if self.mouse_cursor[0] >= self.grid_area[0] as f64
            && self.mouse_cursor[1] >= self.grid_area[1] as f64
            && self.mouse_cursor[0] < (self.grid_area[0] + self.grid_area[2]) as f64
            && self.mouse_cursor[1] < (self.grid_area[1] + self.grid_area[3]) as f64
        {
            position = Some([
                ((self.mouse_cursor[0] - self.grid_area[0] as f64) / self.settings.space_size as f64) as u8,
                ((self.mouse_cursor[1] - self.grid_area[1] as f64) / self.settings.space_size as f64) as u8,
            ]);
        } else {
            position = None;
        }

        position
    }
}

<commit_msg>More usage of player get_space_state function<commit_after>use piston_window::UpdateArgs;
use rand::{Rng, thread_rng};

use player::{Player, Ship};
use settings::Settings;

pub struct App {
    pub settings: Settings,
    pub players: [Player; 2],
    pub state: u8,
    pub turn: u8,
    pub turn_active: bool,
    pub game_over: bool,
    pub winner: Option<u8>,
    pub turn_end_timer: f64,
    pub cpu_turn_timer: f64,
    pub grid_area: [u32; 4],
    pub window_size: [u32; 2],
    mouse_cursor: [f64; 2],
    pub ship_temp_pos: Vec<[u8; 2]>,
    ship_temp_dir: u8,
}

impl App {
    pub fn new(space_size: u32, width: u8, height: u8) -> App {
        let grid_area = [
            space_size,
            space_size * 3,
            width as u32 * space_size,
            height as u32 * space_size
        ];

        let window_size = [
            grid_area[2] + space_size * 2,
            grid_area[3] + space_size * 4
        ];

        App {
            settings: Settings {
                space_size: space_size,
                width: width,
                height: height,
            },
            players: [Player::new(false), Player::new(true)],
            state: 0,
            turn: 0,
            turn_active: true,
            game_over: false,
            winner: None,
            turn_end_timer: 0.0,
            cpu_turn_timer: 0.0,
            grid_area: grid_area,
            window_size: window_size,
            mouse_cursor: [0.0; 2],
            ship_temp_pos: vec![[0, 0], [1, 0]],
            ship_temp_dir: 0,
        }
    }

    pub fn update(&mut self, u: &UpdateArgs) {
        if self.state == 0 {
            if self.players[self.turn as usize].is_cpu {
                self.players[self.turn as usize].cpu_place_ships();
            }

            if self.players[self.turn as usize].ships.len() == 4 {
                self.switch_turn();
            }

            // All ships have been placed; start the game.
            if self.players[0].ships.len() == 4 && self.players[1].ships.len() == 4 {
                self.state = 1;
            }
        } else if self.state == 1 || self.state == 2 {
            if !self.turn_active && !self.game_over {

                // Continue/end the end-of-turn delay.
                if self.turn_end_timer < 1.5 {
                    self.turn_end_timer += u.dt;
                } else {
                    if self.state != 2 {
                        self.switch_turn();
                    } else {
                        self.game_over = true;
                        self.winner = Some(self.turn);
                    }
                }
            }

            // Continue/end the delay when CPU players take their turn.
            if self.turn_active && self.players[self.turn as usize].is_cpu {
                self.cpu_turn_timer += u.dt;
                if self.cpu_turn_timer >= 1.0 {
                    self.cpu_turn_timer = 0.0;
                    let cpu_space = self.cpu_select_space();
                    self.select_space(cpu_space[0], cpu_space[1]);
                }
            }
        }
    }

    /// Selects a space, and performs related checks on the status of ships if
    /// there's a hit.
    fn select_space(&mut self, x: u8, y: u8) {
        let ref mut opponent = self.players[self.not_turn()];
        let mut space_state = 1;
        let mut hit_ship = None;
        for (i, ship) in opponent.ships.iter().enumerate() {
            if ship.position.contains(&[x, y]) {
                space_state = 2;
                hit_ship = Some(i);
            }
        }

        let space = opponent.spaces.iter().position(|space| space.position == [x, y]).unwrap();
        opponent.spaces[space].state = space_state;

        if space_state == 2 {
            // Check if this ship has sunk.
            let hit_ship = hit_ship.unwrap();
            let mut ship_state = false;
            for ship_pos in &opponent.ships[hit_ship].position {
                if opponent.get_space_state(ship_pos[0], ship_pos[1]) == Some(0) {
                    ship_state = true;
                }
            }

            if !ship_state {
                opponent.ships[hit_ship].state = ship_state;
            }

            // Check if any ships are left.
            let mut all_sunk = true;
            for ship in &opponent.ships {
                if ship.state {
                    all_sunk = false;
                    break;
                }
            }

            if all_sunk {
                self.state = 2;
            }
        }

        self.turn_active = false;
    }

    /// Uses RNG to select a space for CPU players.
    fn cpu_select_space(&mut self) -> [u8; 2] {
        let mut rng = thread_rng();
        let mut first_hit = None;
        let mut select = vec![];

        // Determines the order of priority of directions to check if there are
        // any hit spaces found.
        let mut directions: [[i32; 2]; 4] = [
            [-1, 0],
            [1, 0],
            [0, -1],
            [0, 1]
        ];
        rng.shuffle(&mut directions);

        let ref opponent = self.players[self.not_turn()];

        for space in &opponent.spaces {
            if space.state == 2 {

                // Get the hit ship.
                let mut ship: Option<&Ship> = None;
                for s in &opponent.ships {
                    if s.position.contains(&space.position) {
                        ship = Some(s);
                        break;
                    }
                }

                // Make sure the hit ship hasn't been sunk.
                if ship.unwrap().state {
                    if first_hit.is_none() {
                        first_hit = Some(space.position);
                    }

                    // Check if this space forms part of a line of hit spaces.
                    // If it does, and the space at the end hasn't been
                    // selected yet, it's a candidate for selection this turn.
                    for check in &directions {
                        let mut xc = space.position[0];
                        let mut yc = space.position[1];

                        while opponent.get_space_state((xc as i32 + check[0]) as u8, (yc as i32 + check[1]) as u8) == Some(2) {
                            xc = (xc as i32 + check[0]) as u8;
                            yc = (yc as i32 + check[1]) as u8;
                        }

                        if opponent.get_space_state((xc as i32 + check[0]) as u8, (yc as i32 + check[1]) as u8) == Some(0) && (xc != space.position[0] || yc != space.position[1]) {
                            select.push([
                                (xc as i32 + check[0]) as u8,
                                (yc as i32 + check[1]) as u8
                            ]);
                            break;
                        }
                    }
                }
            }
        }

        // If a hit space was found, but no hit spaces next to it, select a
        // non-selected space next to it.
        if first_hit.is_some() && select.len() == 0 {
            let first_hit = first_hit.unwrap();
            for check in &directions {
                if opponent.get_space_state((first_hit[0] as i32 + check[0]) as u8, (first_hit[1] as i32 + check[1]) as u8) == Some(0) {
                    select.push([
                        (first_hit[0] as i32 + check[0]) as u8,
                        (first_hit[1] as i32 + check[1]) as u8
                    ]);
                    break;
                }
            }
        }

        // If no spaces were selected to check, just check any available space.
        if select.len() == 0 {
            let mut x: u8 = rng.gen_range(0, self.settings.width);
            let mut y: u8 = rng.gen_range(0, self.settings.height);
            let mut space_state = opponent.get_space_state(x, y);

            while space_state != Some(0) {
                x = rng.gen_range(0, self.settings.width);
                y = rng.gen_range(0, self.settings.height);
                space_state = opponent.get_space_state(x, y);
            }

            select.push([x, y]);
        }

        // The way the potential selections are chosen, empty spaces to the
        // right or bottom of a line of hit spaces will always be chosen first,
        // so the list of selections should be shuffled.
        if select.len() > 1 {
            select.dedup();
            rng.shuffle(&mut select);
        }

        select[0]
    }

    /// Returns the player who is not currently taking their turn.
    pub fn not_turn(&self) -> usize {
        (self.turn + 1) as usize % 2
    }

    /// When a player's turn is finished, this sets the other player as active.
    fn switch_turn(&mut self) {
        self.turn = self.not_turn() as u8;
        self.turn_active = true;
        self.turn_end_timer = 0.0;
    }

    /// Processes all left mouse clicks according to the current program state.
    pub fn mouse_left_click(&mut self) {
        if let Some(grid_pos) = self.mouse_cursor_grid_position() {
            if self.state == 0 && self.turn_active && !self.players[self.turn as usize].is_cpu {

                // State 0: place ships.
                let mut ship = vec![];
                for pos in &self.ship_temp_pos {
                    ship.push(*pos);
                }

                if self.players[self.turn as usize].valid_ship_position(&ship) {
                    self.players[self.turn as usize].ships.push(Ship {
                        position: ship,
                        state: true,
                    });
                }
            } else if self.state == 1 && self.turn_active && !self.players[self.turn as usize].is_cpu {

                // State 1: select spaces on opponent's grid.
                if self.players[self.not_turn()].get_space_state(grid_pos[0], grid_pos[1]) == Some(0) {
                    self.select_space(grid_pos[0], grid_pos[1]);
                }
            }
        }
    }

    /// Processes all right mouse clicks according to the current program state.
    pub fn mouse_right_click(&mut self) {
        if self.state == 0 {

            // State 0: rotate ships.
            let mut new_ship_pos = vec![self.ship_temp_pos[0]];
            match self.ship_temp_dir {
                0 => {
                    self.ship_temp_dir = 1;
                    let length = self.players[self.turn as usize].ships.len() + 2;
                    for pos in 1..length {
                        new_ship_pos.push([self.ship_temp_pos[0][0], self.ship_temp_pos[0][1] + pos as u8]);
                    }
                },
                1 => {
                    self.ship_temp_dir = 0;
                    let length = self.players[self.turn as usize].ships.len() + 2;
                    for pos in 1..length {
                        new_ship_pos.push([self.ship_temp_pos[0][0] + pos as u8, self.ship_temp_pos[0][1]]);
                    }
                },
                _ => {}
            }

            self.ship_temp_pos = new_ship_pos;
        }
    }

    /// Records the last known mouse cursor position.
    pub fn mouse_cursor_movement(&mut self, c: &[f64; 2]) {
        self.mouse_cursor = *c;

        if let Some(grid_pos) = self.mouse_cursor_grid_position() {
            let ref mut player = self.players[self.turn as usize];

            // During ship placement, set the temporary ship position so it can
            // be drawn by the window renderer.
            if self.state == 0 {
                self.ship_temp_pos = player.get_ship_position(
                    grid_pos,
                    self.ship_temp_dir,
                    player.ships.len() as u8 + 2
                );
            }

            // State 1: set the player's grid cursor.
            if self.state == 1 {
                if self.turn_end_timer == 0.0 && !player.is_cpu {
                    player.grid_cursor = grid_pos;
                }
            }
        }
    }

    /// Returns the grid coordinates of the mouse cursor position.
    fn mouse_cursor_grid_position(&self) -> Option<[u8; 2]> {
        let position: Option<[u8; 2]>;
        if self.mouse_cursor[0] >= self.grid_area[0] as f64
            && self.mouse_cursor[1] >= self.grid_area[1] as f64
            && self.mouse_cursor[0] < (self.grid_area[0] + self.grid_area[2]) as f64
            && self.mouse_cursor[1] < (self.grid_area[1] + self.grid_area[3]) as f64
        {
            position = Some([
                ((self.mouse_cursor[0] - self.grid_area[0] as f64) / self.settings.space_size as f64) as u8,
                ((self.mouse_cursor[1] - self.grid_area[1] as f64) / self.settings.space_size as f64) as u8,
            ]);
        } else {
            position = None;
        }

        position
    }
}

<|endoftext|>


<commit_before>#[macro_use]
extern crate log;

use cabot;

use std::collections::HashMap;
use std::iter::FromIterator;
use std::net::{AddrParseError, SocketAddr};
use std::pin::Pin;

use async_std;
use async_std::fs::OpenOptions;
use async_std::io::{self, Write};
use async_std::prelude::*;
use async_std::task::Context;
use async_std::task::Poll;
use clap::{App, Arg};
use log::Level::Info;

use cabot::constants;
use cabot::http;
use cabot::request::RequestBuilder;
use cabot::results::CabotResult;

pub async fn run() -> CabotResult<()> {
    let user_agent: String = constants::user_agent();
    let matches = App::new(env!("CARGO_PKG_NAME"))
        .version(constants::VERSION)
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .arg(
            Arg::with_name("URL")
                .index(1)
                .required(true)
                .help("URL to request"),
        )
        .arg(
            Arg::with_name("REQUEST")
                .short("X")
                .long("request")
                .default_value("GET")
                .help("Specify request command to use"),
        )
        .arg(
            Arg::with_name("HEADER")
                .short("H")
                .long("header")
                .takes_value(true)
                .multiple(true)
                .help("Pass custom header to server"),
        )
        .arg(
            Arg::with_name("FILE")
                .short("o")
                .long("output")
                .takes_value(true)
                .help("Write to FILE instead of stdout"),
        )
        .arg(
            Arg::with_name("VERBOSE")
                .short("v")
                .long("verbose")
                .help("Make the operation more talkative"),
        )
        .arg(
            Arg::with_name("BODY")
                .short("d")
                .long("data")
                .takes_value(true)
                .help("Post Data (Using utf-8 encoding)"),
        )
        .arg(
            Arg::with_name("IPv4")
                .short("4")
                .long("ipv4")
                .help("Resolve host names to IPv4 addresses"),
        )
        .arg(
            Arg::with_name("IPv6")
                .short("6")
                .long("ipv6")
                .help("Resolve host names to IPv6 addresses"),
        )
        .arg(
            Arg::with_name("UA")
                .short("A")
                .long("user-agent")
                .default_value(user_agent.as_str())
                .help("The user-agent HTTP header to use"),
        )
        .arg(
            Arg::with_name("DNS_LOOKUP_TIMEOUT")
                .long("dns-timeout")
                .takes_value(true)
                .default_value("5")
                .help("timeout for the dns lookup resolution in seconds"),
        )
        .arg(
            Arg::with_name("CONNECT_TIMEOUT")
                .long("connect-timeout")
                .takes_value(true)
                .default_value("15")
                .help("timeout for the tcp connection"),
        )
        .arg(
            Arg::with_name("READ_TIMEOUT")
                .long("read-timeout")
                .takes_value(true)
                .default_value("10")
                .help("timeout for the tcp read in seconds"),
        )
        .arg(
            Arg::with_name("REQUEST_TIMEOUT")
                .long("timeout")
                .takes_value(true)
                .default_value("0")
                .help("timeout for the whole http request in seconds (0 means no timeout)"),
        )
        .arg(
            Arg::with_name("RESOLVE")
                .long("resolve")
                .takes_value(true)
                .multiple(true)
                .help("<host:port:address> Resolve the host+port to this address"),
        )
        .get_matches();

    let url = matches.value_of("URL").unwrap();
    let http_method = matches.value_of("REQUEST").unwrap();
    let verbose = matches.is_present("VERBOSE");
    let body = matches.value_of("BODY");
    let ua = matches.value_of("UA").unwrap();

    let mut ipv4 = matches.is_present("IPv4");
    let mut ipv6 = matches.is_present("IPv6");

    if !ipv4 && !ipv6 {
        ipv4 = true;
        ipv6 = true;
    }

    let headers: Vec<&str> = match matches.values_of("HEADER") {
        Some(headers) => headers.collect(),
        None => Vec::new(),
    };

    let resolved: HashMap<String, SocketAddr> = match matches.values_of("RESOLVE") {
        Some(headers) => HashMap::from_iter(
            headers
                .map(|resolv| resolv.splitn(3, ':'))
                .map(|resolv| {
                    let count = resolv.clone().count();
                    if count != 3 {
                        let resolv: Vec<&str> = resolv.collect();
                        let _ =
                            eprintln!("Invalid format in resolve argument: {}", resolv.join(":"));
                        std::process::exit(1);
                    }
                    resolv
                })
                .map(|mut resolv| {
                    (
                        resolv.next().unwrap(),
                        resolv.next().unwrap(),
                        resolv.next().unwrap(),
                    )
                })
                .map(|(host, port, addr)| {
                    let parsed_port = port.parse::<usize>();
                    if parsed_port.is_err() {
                        let _ = eprintln!(
                            "Invalid port in resolve argument: {}:{}:{}",
                            host, port, addr
                        );
                        std::process::exit(1);
                    }
                    let sockaddr: Result<SocketAddr, AddrParseError> =
                        format!("{}:{}", addr, port).parse();
                    if sockaddr.is_err() {
                        let _ = eprintln!(
                            "Invalid address in resolve argument: {}:{}:{}",
                            host, port, addr
                        );
                        std::process::exit(1);
                    }
                    let sockaddr = sockaddr.unwrap();
                    (format!("{}:{}", host, port), sockaddr)
                }),
        ),
        None => HashMap::new(),
    };

    let dns_timeout = u64::from_str_radix(matches.value_of("DNS_LOOKUP_TIMEOUT").unwrap(), 10)
        .expect("DNS_LOOKUP_TIMEOUT must be an integer")
        * 1_000;
    let connect_timeout = u64::from_str_radix(matches.value_of("CONNECT_TIMEOUT").unwrap(), 10)
        .expect("CONNECT_TIMEOUT must be an integer")
        * 1_000;
    let read_timeout = u64::from_str_radix(matches.value_of("READ_TIMEOUT").unwrap(), 10)
        .expect("READ_TIMEOUT must be an integer")
        * 1_000;
    let request_timeout = u64::from_str_radix(matches.value_of("REQUEST_TIMEOUT").unwrap(), 10)
        .expect("REQUEST_TIMEOUT must be an integer")
        * 1_000;

    let mut builder = RequestBuilder::new(url)
        .set_http_method(http_method)
        .set_user_agent(ua)
        .add_headers(&headers.as_slice());

    if body.is_some() {
        builder = builder.set_body_as_str(body.unwrap());
    }

    let request = builder.build()?;

    if let Some(path) = matches.value_of("FILE") {
        let mut f = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(path)
            .await
            .unwrap();
        http::http_query(
            &request,
            &mut CabotBinWrite::new(&mut f, verbose),
            &resolved,
            verbose,
            ipv4,
            ipv6,
            dns_timeout,
            connect_timeout,
            read_timeout,
            request_timeout,
            constants::NUMBER_OF_REDIRECT,
        )
        .await?
    } else {
        http::http_query(
            &request,
            &mut CabotBinWrite::new(&mut io::stdout(), verbose),
            &resolved,
            verbose,
            ipv4,
            ipv6,
            dns_timeout,
            connect_timeout,
            read_timeout,
            request_timeout,
            constants::NUMBER_OF_REDIRECT,
        )
        .await?
    };
    Ok(())
}

#[async_std::main]
async fn main() {
    #[cfg(feature = "pretty_log")]
    pretty_env_logger::init();
    debug!("Starting cabot");

    run()
        .await
        .map(|ok| {
            debug!("Command cabot ended succesfully");
            ok
        })
        .map_err(|err| {
            eprintln!("{}", err);
            std::process::exit(1);
        })
        .unwrap();
}

// Internal Of the Binary

struct CabotBinWrite<'a> {
    out: &'a mut (dyn Write + Unpin),
    header_read: bool,
    verbose: bool,
}

impl<'a> CabotBinWrite<'a> {
    pub fn new(out: &'a mut (dyn Write + Unpin), verbose: bool) -> Self {
        CabotBinWrite {
            out,
            verbose,
            header_read: false,
        }
    }
    fn display_headers(&self, buf: &[u8]) {
        let headers = String::from_utf8_lossy(buf);
        let split: Vec<&str> = constants::SPLIT_HEADER_RE.split(&headers).collect();
        if log_enabled!(Info) {
            for part in split {
                info!("< {}", part);
            }
        } else if self.verbose {
            for part in split {
                eprintln!("< {}", part);
            }
        }
    }
}

impl<'a> Write for CabotBinWrite<'a> {
    // may receive headers
    fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {
        let self_ = Pin::get_mut(self);
        if !self_.header_read {
            // the first time write is called, all headers are sent
            // in the buffer. there is no need to parse it again.
            if log_enabled!(Info) || self_.verbose {
                self_.display_headers(&buf);
            }
            self_.header_read = true;
            Poll::Ready(Ok(0))
        } else {
            let towrite = buf.len();
            let mut written = 0;
            loop {
                let res = Pin::new(&mut self_.out).poll_write(cx, &buf[written..towrite]);
                match res {
                    Poll::Ready(Ok(l)) => written += l,
                    _ => {}
                }
                if written >= towrite {
                    break;
                }
            }
            Poll::Ready(Ok(written))
        }
    }

    /// this function is called when the request is done
    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<io::Result<()>> {
        let self_ = Pin::get_mut(self);
        self_.out.flush();
        Poll::Ready(Ok(()))
    }

    fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
        let self_ = Pin::get_mut(self);
        Pin::new(&mut self_.out).poll_close(cx)
    }
}
<commit_msg>Make the number of redirection configuration<commit_after>#[macro_use]
extern crate log;

use cabot;

use std::collections::HashMap;
use std::iter::FromIterator;
use std::net::{AddrParseError, SocketAddr};
use std::pin::Pin;

use async_std;
use async_std::fs::OpenOptions;
use async_std::io::{self, Write};
use async_std::prelude::*;
use async_std::task::Context;
use async_std::task::Poll;
use clap::{App, Arg};
use log::Level::Info;

use cabot::constants;
use cabot::http;
use cabot::request::RequestBuilder;
use cabot::results::CabotResult;

pub async fn run() -> CabotResult<()> {
    let user_agent: String = constants::user_agent();
    let number_of_redirect = format!("{}", constants::NUMBER_OF_REDIRECT);
    let matches = App::new(env!("CARGO_PKG_NAME"))
        .version(constants::VERSION)
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .arg(
            Arg::with_name("URL")
                .index(1)
                .required(true)
                .help("URL to request"),
        )
        .arg(
            Arg::with_name("REQUEST")
                .short("X")
                .long("request")
                .default_value("GET")
                .help("Specify request command to use"),
        )
        .arg(
            Arg::with_name("HEADER")
                .short("H")
                .long("header")
                .takes_value(true)
                .multiple(true)
                .help("Pass custom header to server"),
        )
        .arg(
            Arg::with_name("FILE")
                .short("o")
                .long("output")
                .takes_value(true)
                .help("Write to FILE instead of stdout"),
        )
        .arg(
            Arg::with_name("VERBOSE")
                .short("v")
                .long("verbose")
                .help("Make the operation more talkative"),
        )
        .arg(
            Arg::with_name("BODY")
                .short("d")
                .long("data")
                .takes_value(true)
                .help("Post Data (Using utf-8 encoding)"),
        )
        .arg(
            Arg::with_name("IPv4")
                .short("4")
                .long("ipv4")
                .help("Resolve host names to IPv4 addresses"),
        )
        .arg(
            Arg::with_name("IPv6")
                .short("6")
                .long("ipv6")
                .help("Resolve host names to IPv6 addresses"),
        )
        .arg(
            Arg::with_name("UA")
                .short("A")
                .long("user-agent")
                .default_value(user_agent.as_str())
                .help("The user-agent HTTP header to use"),
        )
        .arg(
            Arg::with_name("DNS_LOOKUP_TIMEOUT")
                .long("dns-timeout")
                .takes_value(true)
                .default_value("5")
                .help("timeout for the dns lookup resolution in seconds"),
        )
        .arg(
            Arg::with_name("CONNECT_TIMEOUT")
                .long("connect-timeout")
                .takes_value(true)
                .default_value("15")
                .help("timeout for the tcp connection"),
        )
        .arg(
            Arg::with_name("READ_TIMEOUT")
                .long("read-timeout")
                .takes_value(true)
                .default_value("10")
                .help("timeout for the tcp read in seconds"),
        )
        .arg(
            Arg::with_name("REQUEST_TIMEOUT")
                .long("timeout")
                .takes_value(true)
                .default_value("0")
                .help("timeout for the whole http request in seconds (0 means no timeout)"),
        )
        .arg(
            Arg::with_name("NUMBER_OF_REDIRECT")
                .long("max-redirs")
                .takes_value(true)
                .default_value(number_of_redirect.as_str())
                .help("max number of redirection before returning a response"),
        )
        .arg(
            Arg::with_name("RESOLVE")
                .long("resolve")
                .takes_value(true)
                .multiple(true)
                .help("<host:port:address> Resolve the host+port to this address"),
        )
        .get_matches();

    let url = matches.value_of("URL").unwrap();
    let http_method = matches.value_of("REQUEST").unwrap();
    let verbose = matches.is_present("VERBOSE");
    let body = matches.value_of("BODY");
    let ua = matches.value_of("UA").unwrap();

    let mut ipv4 = matches.is_present("IPv4");
    let mut ipv6 = matches.is_present("IPv6");

    if !ipv4 && !ipv6 {
        ipv4 = true;
        ipv6 = true;
    }

    let headers: Vec<&str> = match matches.values_of("HEADER") {
        Some(headers) => headers.collect(),
        None => Vec::new(),
    };

    let resolved: HashMap<String, SocketAddr> = match matches.values_of("RESOLVE") {
        Some(headers) => HashMap::from_iter(
            headers
                .map(|resolv| resolv.splitn(3, ':'))
                .map(|resolv| {
                    let count = resolv.clone().count();
                    if count != 3 {
                        let resolv: Vec<&str> = resolv.collect();
                        let _ =
                            eprintln!("Invalid format in resolve argument: {}", resolv.join(":"));
                        std::process::exit(1);
                    }
                    resolv
                })
                .map(|mut resolv| {
                    (
                        resolv.next().unwrap(),
                        resolv.next().unwrap(),
                        resolv.next().unwrap(),
                    )
                })
                .map(|(host, port, addr)| {
                    let parsed_port = port.parse::<usize>();
                    if parsed_port.is_err() {
                        let _ = eprintln!(
                            "Invalid port in resolve argument: {}:{}:{}",
                            host, port, addr
                        );
                        std::process::exit(1);
                    }
                    let sockaddr: Result<SocketAddr, AddrParseError> =
                        format!("{}:{}", addr, port).parse();
                    if sockaddr.is_err() {
                        let _ = eprintln!(
                            "Invalid address in resolve argument: {}:{}:{}",
                            host, port, addr
                        );
                        std::process::exit(1);
                    }
                    let sockaddr = sockaddr.unwrap();
                    (format!("{}:{}", host, port), sockaddr)
                }),
        ),
        None => HashMap::new(),
    };

    let dns_timeout = u64::from_str_radix(matches.value_of("DNS_LOOKUP_TIMEOUT").unwrap(), 10)
        .expect("DNS_LOOKUP_TIMEOUT must be an integer")
        * 1_000;
    let connect_timeout = u64::from_str_radix(matches.value_of("CONNECT_TIMEOUT").unwrap(), 10)
        .expect("CONNECT_TIMEOUT must be an integer")
        * 1_000;
    let read_timeout = u64::from_str_radix(matches.value_of("READ_TIMEOUT").unwrap(), 10)
        .expect("READ_TIMEOUT must be an integer")
        * 1_000;
    let request_timeout = u64::from_str_radix(matches.value_of("REQUEST_TIMEOUT").unwrap(), 10)
        .expect("REQUEST_TIMEOUT must be an integer")
        * 1_000;
    let number_of_redirect =
        u8::from_str_radix(matches.value_of("NUMBER_OF_REDIRECT").unwrap(), 10)
            .expect("NUMBER_OF_REDIRECT must be an integer");

    let mut builder = RequestBuilder::new(url)
        .set_http_method(http_method)
        .set_user_agent(ua)
        .add_headers(&headers.as_slice());

    if body.is_some() {
        builder = builder.set_body_as_str(body.unwrap());
    }

    let request = builder.build()?;

    if let Some(path) = matches.value_of("FILE") {
        let mut f = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(path)
            .await
            .unwrap();
        http::http_query(
            &request,
            &mut CabotBinWrite::new(&mut f, verbose),
            &resolved,
            verbose,
            ipv4,
            ipv6,
            dns_timeout,
            connect_timeout,
            read_timeout,
            request_timeout,
            number_of_redirect,
        )
        .await?
    } else {
        http::http_query(
            &request,
            &mut CabotBinWrite::new(&mut io::stdout(), verbose),
            &resolved,
            verbose,
            ipv4,
            ipv6,
            dns_timeout,
            connect_timeout,
            read_timeout,
            request_timeout,
            number_of_redirect,
        )
        .await?
    };
    Ok(())
}

#[async_std::main]
async fn main() {
    #[cfg(feature = "pretty_log")]
    pretty_env_logger::init();
    debug!("Starting cabot");

    run()
        .await
        .map(|ok| {
            debug!("Command cabot ended succesfully");
            ok
        })
        .map_err(|err| {
            eprintln!("{}", err);
            std::process::exit(1);
        })
        .unwrap();
}

// Internal Of the Binary

struct CabotBinWrite<'a> {
    out: &'a mut (dyn Write + Unpin),
    header_read: bool,
    verbose: bool,
}

impl<'a> CabotBinWrite<'a> {
    pub fn new(out: &'a mut (dyn Write + Unpin), verbose: bool) -> Self {
        CabotBinWrite {
            out,
            verbose,
            header_read: false,
        }
    }
    fn display_headers(&self, buf: &[u8]) {
        let headers = String::from_utf8_lossy(buf);
        let split: Vec<&str> = constants::SPLIT_HEADER_RE.split(&headers).collect();
        if log_enabled!(Info) {
            for part in split {
                info!("< {}", part);
            }
        } else if self.verbose {
            for part in split {
                eprintln!("< {}", part);
            }
        }
    }
}

impl<'a> Write for CabotBinWrite<'a> {
    // may receive headers
    fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {
        let self_ = Pin::get_mut(self);
        if !self_.header_read {
            // the first time write is called, all headers are sent
            // in the buffer. there is no need to parse it again.
            if log_enabled!(Info) || self_.verbose {
                self_.display_headers(&buf);
            }
            self_.header_read = true;
            Poll::Ready(Ok(0))
        } else {
            let towrite = buf.len();
            let mut written = 0;
            loop {
                let res = Pin::new(&mut self_.out).poll_write(cx, &buf[written..towrite]);
                match res {
                    Poll::Ready(Ok(l)) => written += l,
                    _ => {}
                }
                if written >= towrite {
                    break;
                }
            }
            Poll::Ready(Ok(written))
        }
    }

    /// this function is called when the request is done
    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<io::Result<()>> {
        let self_ = Pin::get_mut(self);
        self_.out.flush();
        Poll::Ready(Ok(()))
    }

    fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
        let self_ = Pin::get_mut(self);
        Pin::new(&mut self_.out).poll_close(cx)
    }
}
<|endoftext|>


<commit_before>use bullet::{BulletDrawable, bullets::Bullets};
use cgmath;
use cgmath::{Deg, Point2};
use character::controls::CharacterInputState;
use critter::CritterData;
use data;
use game::constants::{ASPECT_RATIO, NORMAL_DEATH_SPRITE_OFFSET, SPRITE_OFFSET, ZOMBIE_SHEET_TOTAL_WIDTH, ZOMBIE_STILL_SPRITE_OFFSET};
use game::get_random_bool;
use gfx;
use gfx_app::{ColorFormat, DepthFormat};
use graphics::{camera::CameraInputState, Dimensions, direction_movement, get_orientation, load_texture, orientation::{Orientation, Stance}, overlaps};
use shaders::{CharacterSheet, critter_pipeline, Position, Projection, VertexData};
use specs;
use specs::prelude::{Read, ReadStorage, WriteStorage};
use terrain::path_finding::calc_next_movement;
use zombie::zombies::Zombies;

pub mod zombies;

const SHADER_VERT: &[u8] = include_bytes!("../shaders/character.v.glsl");
const SHADER_FRAG: &[u8] = include_bytes!("../shaders/character.f.glsl");

#[derive(Debug, Clone)]
pub struct ZombieDrawable {
  projection: Projection,
  pub position: Position,
  previous_position: Position,
  orientation: Orientation,
  pub stance: Stance,
  direction: Orientation,
  pub movement_direction: Point2<f32>,
  zombie_idx: usize,
  zombie_death_idx: usize,
  is_colliding: bool,
}

impl ZombieDrawable {
  pub fn new(position: Position) -> ZombieDrawable {
    let view = Dimensions::get_view_matrix();

    ZombieDrawable {
      projection: Projection {
        model: view.into(),
        view: view.into(),
        proj: cgmath::perspective(Deg(60.0f32), ASPECT_RATIO, 0.1, 4000.0).into(),
      },
      position,
      previous_position: Position::new([0.0, 0.0]),
      orientation: Orientation::Left,
      stance: Stance::Still,
      direction: Orientation::Left,
      movement_direction: Point2::new(0.0, 0.0),
      zombie_idx: 0,
      zombie_death_idx: 0,
      is_colliding: false,
    }
  }

  pub fn update(&mut self, world_to_clip: &Projection, ci: &CharacterInputState) {
    self.projection = *world_to_clip;

    let offset_delta =
      Position::new([
        ci.x_movement - self.previous_position.position[0],
        ci.y_movement - self.previous_position.position[1]
      ]);

    self.previous_position = Position::new([
      ci.x_movement,
      ci.y_movement
    ]);
    let movement_speed = 1.4;

    let is_alive = self.stance != Stance::NormalDeath && self.stance != Stance::CriticalDeath;
    if is_alive {
      let zombie_pos = Position::new([ci.x_movement - self.position.position[0], ci.y_movement - self.position.position[1]]);

      let dir = calc_next_movement(zombie_pos, self.previous_position) as u32;

      self.direction = get_orientation(dir);

      self.movement_direction = direction_movement(dir);
      self.stance = Stance::Walking;
    } else {
      self.movement_direction = Point2::new(0.0, 0.0);
    }

    let new_pos = Position::new([
      self.position.position[0] + offset_delta.position[0] + (self.movement_direction.x * movement_speed),
      self.position.position[1] + offset_delta.position[1] + (self.movement_direction.y * movement_speed)
    ]);

    self.position = new_pos;
  }

  pub fn check_bullet_hits(&mut self, bullets: &[BulletDrawable]) {
    bullets.iter().for_each(|bullet| {
      if overlaps(self.position, bullet.position, 20.0, 40.0) && self.stance != Stance::NormalDeath && self.stance != Stance::CriticalDeath {
        self.stance =
          if get_random_bool() {
            Stance::NormalDeath
          } else {
            Stance::CriticalDeath
          };
      }
    });
  }

  pub fn update_alive_idx(&mut self, max_idx: usize) {
    if self.zombie_idx < max_idx {
      self.zombie_idx += 1;
    } else {
      self.zombie_idx = 0;
    }
  }

  pub fn update_death_idx(&mut self, max_idx: usize) {
    if self.zombie_death_idx < max_idx {
      self.zombie_death_idx += 1;
    }
  }
}

pub struct ZombieDrawSystem<R: gfx::Resources> {
  bundle: gfx::pso::bundle::Bundle<R, critter_pipeline::Data<R>>,
  data: Vec<CritterData>,
}

impl<R: gfx::Resources> ZombieDrawSystem<R> {
  pub fn new<F>(factory: &mut F,
                rtv: gfx::handle::RenderTargetView<R, ColorFormat>,
                dsv: gfx::handle::DepthStencilView<R, DepthFormat>) -> ZombieDrawSystem<R>
                where F: gfx::Factory<R> {
    use gfx::traits::FactoryExt;

    let zombie_bytes = include_bytes!("../../assets/zombie.png");

    let vertex_data: [VertexData; 4] = [
      VertexData::new([-25.0, -35.0], [0.0, 1.0]),
      VertexData::new([25.0, -35.0], [1.0, 1.0]),
      VertexData::new([25.0, 35.0], [1.0, 0.0]),
      VertexData::new([-25.0, 35.0], [0.0, 0.0]),
    ];

    let index_data: [u16; 6] = [0, 1, 2, 2, 3, 0];

    let (vertex_buf, slice) = factory.create_vertex_buffer_with_slice(&vertex_data[..], &index_data[..]);

    let char_texture = load_texture(factory, zombie_bytes);
    let pso = factory
      .create_pipeline_simple(SHADER_VERT,
                              SHADER_FRAG,
                              critter_pipeline::new())
      .unwrap();

    let pipeline_data = critter_pipeline::Data {
      vbuf: vertex_buf,
      projection_cb: factory.create_constant_buffer(1),
      position_cb: factory.create_constant_buffer(1),
      character_sprite_cb: factory.create_constant_buffer(1),
      charactersheet: (char_texture, factory.create_sampler_linear()),
      out_color: rtv,
      out_depth: dsv,
    };

    let data = data::load_zombie();

    ZombieDrawSystem {
      bundle: gfx::Bundle::new(slice, pso, pipeline_data),
      data,
    }
  }

  fn get_next_sprite(&self, drawable: &mut ZombieDrawable) -> CharacterSheet {
    let zombie_sprite = match drawable.stance {
      Stance::Still => {
        let sprite_idx = (drawable.direction as usize * 4 + drawable.zombie_idx) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
      Stance::Walking if drawable.orientation != Orientation::Still => {
        let sprite_idx = (drawable.direction as usize * 8 + drawable.zombie_idx + ZOMBIE_STILL_SPRITE_OFFSET) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
      Stance::NormalDeath if drawable.orientation != Orientation::Still => {
        let sprite_idx = (drawable.direction as usize * 6 + drawable.zombie_death_idx + NORMAL_DEATH_SPRITE_OFFSET) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
      Stance::CriticalDeath if drawable.orientation != Orientation::Still => {
        let sprite_idx = (drawable.direction as usize * 8 + drawable.zombie_death_idx) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
      _ => {
        drawable.direction = drawable.orientation;
        let sprite_idx = (drawable.orientation as usize * 8 + drawable.zombie_idx + ZOMBIE_STILL_SPRITE_OFFSET) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
    };

    let (y_div, row_idx) =
      if drawable.stance == Stance::NormalDeath || drawable.stance == Stance::CriticalDeath {
        (0.0, 2)
      } else {
        (1.0, 2)
      };

    let elements_x = ZOMBIE_SHEET_TOTAL_WIDTH / (zombie_sprite.0.data[2] + SPRITE_OFFSET);
    CharacterSheet {
      x_div: elements_x,
      y_div,
      row_idx,
      index: zombie_sprite.1 as f32,
    }
  }

  pub fn draw<C>(&mut self,
                 mut drawable: &mut ZombieDrawable,
                 encoder: &mut gfx::Encoder<R, C>)
                 where C: gfx::CommandBuffer<R> {
    encoder.update_constant_buffer(&self.bundle.data.projection_cb, &drawable.projection);
    encoder.update_constant_buffer(&self.bundle.data.position_cb, &drawable.position);
    encoder.update_constant_buffer(&self.bundle.data.character_sprite_cb,
                                   &self.get_next_sprite(&mut drawable));
    self.bundle.encode(encoder);
  }
}

#[derive(Debug)]
pub struct PreDrawSystem;

impl PreDrawSystem {
  pub fn new() -> PreDrawSystem {
    PreDrawSystem {}
  }
}

impl<'a> specs::prelude::System<'a> for PreDrawSystem {
  #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
  type SystemData = (WriteStorage<'a, Zombies>,
                     ReadStorage<'a, CameraInputState>,
                     ReadStorage<'a, CharacterInputState>,
                     ReadStorage<'a, Bullets>,
                     Read<'a, Dimensions>);

  fn run(&mut self, (mut zombies, camera_input, character_input, bullets, dim): Self::SystemData) {
    use specs::join::Join;

    for (zs, camera, ci, bs) in (&mut zombies, &camera_input, &character_input, &bullets).join() {
      let world_to_clip = dim.world_to_projection(camera);

      for z in &mut zs.zombies {
        z.update(&world_to_clip, ci);
        z.check_bullet_hits(&bs.bullets);
      }
    }
  }
}
<commit_msg>Adjust bullet hit detection<commit_after>use bullet::{BulletDrawable, bullets::Bullets};
use cgmath;
use cgmath::{Deg, Point2};
use character::controls::CharacterInputState;
use critter::CritterData;
use data;
use game::constants::{ASPECT_RATIO, NORMAL_DEATH_SPRITE_OFFSET, SPRITE_OFFSET, ZOMBIE_SHEET_TOTAL_WIDTH, ZOMBIE_STILL_SPRITE_OFFSET};
use game::get_random_bool;
use gfx;
use gfx_app::{ColorFormat, DepthFormat};
use graphics::{camera::CameraInputState, Dimensions, direction_movement, get_orientation, load_texture, orientation::{Orientation, Stance}, overlaps};
use shaders::{CharacterSheet, critter_pipeline, Position, Projection, VertexData};
use specs;
use specs::prelude::{Read, ReadStorage, WriteStorage};
use terrain::path_finding::calc_next_movement;
use zombie::zombies::Zombies;

pub mod zombies;

const SHADER_VERT: &[u8] = include_bytes!("../shaders/character.v.glsl");
const SHADER_FRAG: &[u8] = include_bytes!("../shaders/character.f.glsl");

#[derive(Debug, Clone)]
pub struct ZombieDrawable {
  projection: Projection,
  pub position: Position,
  previous_position: Position,
  orientation: Orientation,
  pub stance: Stance,
  direction: Orientation,
  pub movement_direction: Point2<f32>,
  zombie_idx: usize,
  zombie_death_idx: usize,
  is_colliding: bool,
}

impl ZombieDrawable {
  pub fn new(position: Position) -> ZombieDrawable {
    let view = Dimensions::get_view_matrix();

    ZombieDrawable {
      projection: Projection {
        model: view.into(),
        view: view.into(),
        proj: cgmath::perspective(Deg(60.0f32), ASPECT_RATIO, 0.1, 4000.0).into(),
      },
      position,
      previous_position: Position::new([0.0, 0.0]),
      orientation: Orientation::Left,
      stance: Stance::Still,
      direction: Orientation::Left,
      movement_direction: Point2::new(0.0, 0.0),
      zombie_idx: 0,
      zombie_death_idx: 0,
      is_colliding: false,
    }
  }

  pub fn update(&mut self, world_to_clip: &Projection, ci: &CharacterInputState) {
    self.projection = *world_to_clip;

    let offset_delta =
      Position::new([
        ci.x_movement - self.previous_position.position[0],
        ci.y_movement - self.previous_position.position[1]
      ]);

    self.previous_position = Position::new([
      ci.x_movement,
      ci.y_movement
    ]);
    let movement_speed = 1.4;

    let is_alive = self.stance != Stance::NormalDeath && self.stance != Stance::CriticalDeath;
    if is_alive {
      let zombie_pos = Position::new([ci.x_movement - self.position.position[0], ci.y_movement - self.position.position[1]]);

      let dir = calc_next_movement(zombie_pos, self.previous_position) as u32;

      self.direction = get_orientation(dir);

      self.movement_direction = direction_movement(dir);
      self.stance = Stance::Walking;
    } else {
      self.movement_direction = Point2::new(0.0, 0.0);
    }

    let new_pos = Position::new([
      self.position.position[0] + offset_delta.position[0] + (self.movement_direction.x * movement_speed),
      self.position.position[1] + offset_delta.position[1] + (self.movement_direction.y * movement_speed)
    ]);

    self.position = new_pos;
  }

  pub fn check_bullet_hits(&mut self, bullets: &[BulletDrawable]) {
    bullets.iter().for_each(|bullet| {
      if overlaps(self.position, bullet.position, 15.0, 15.0) && self.stance != Stance::NormalDeath && self.stance != Stance::CriticalDeath {
        self.stance =
          if get_random_bool() {
            Stance::NormalDeath
          } else {
            Stance::CriticalDeath
          };
      }
    });
  }

  pub fn update_alive_idx(&mut self, max_idx: usize) {
    if self.zombie_idx < max_idx {
      self.zombie_idx += 1;
    } else {
      self.zombie_idx = 0;
    }
  }

  pub fn update_death_idx(&mut self, max_idx: usize) {
    if self.zombie_death_idx < max_idx {
      self.zombie_death_idx += 1;
    }
  }
}

pub struct ZombieDrawSystem<R: gfx::Resources> {
  bundle: gfx::pso::bundle::Bundle<R, critter_pipeline::Data<R>>,
  data: Vec<CritterData>,
}

impl<R: gfx::Resources> ZombieDrawSystem<R> {
  pub fn new<F>(factory: &mut F,
                rtv: gfx::handle::RenderTargetView<R, ColorFormat>,
                dsv: gfx::handle::DepthStencilView<R, DepthFormat>) -> ZombieDrawSystem<R>
                where F: gfx::Factory<R> {
    use gfx::traits::FactoryExt;

    let zombie_bytes = include_bytes!("../../assets/zombie.png");

    let vertex_data: [VertexData; 4] = [
      VertexData::new([-25.0, -35.0], [0.0, 1.0]),
      VertexData::new([25.0, -35.0], [1.0, 1.0]),
      VertexData::new([25.0, 35.0], [1.0, 0.0]),
      VertexData::new([-25.0, 35.0], [0.0, 0.0]),
    ];

    let index_data: [u16; 6] = [0, 1, 2, 2, 3, 0];

    let (vertex_buf, slice) = factory.create_vertex_buffer_with_slice(&vertex_data[..], &index_data[..]);

    let char_texture = load_texture(factory, zombie_bytes);
    let pso = factory
      .create_pipeline_simple(SHADER_VERT,
                              SHADER_FRAG,
                              critter_pipeline::new())
      .unwrap();

    let pipeline_data = critter_pipeline::Data {
      vbuf: vertex_buf,
      projection_cb: factory.create_constant_buffer(1),
      position_cb: factory.create_constant_buffer(1),
      character_sprite_cb: factory.create_constant_buffer(1),
      charactersheet: (char_texture, factory.create_sampler_linear()),
      out_color: rtv,
      out_depth: dsv,
    };

    let data = data::load_zombie();

    ZombieDrawSystem {
      bundle: gfx::Bundle::new(slice, pso, pipeline_data),
      data,
    }
  }

  fn get_next_sprite(&self, drawable: &mut ZombieDrawable) -> CharacterSheet {
    let zombie_sprite = match drawable.stance {
      Stance::Still => {
        let sprite_idx = (drawable.direction as usize * 4 + drawable.zombie_idx) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
      Stance::Walking if drawable.orientation != Orientation::Still => {
        let sprite_idx = (drawable.direction as usize * 8 + drawable.zombie_idx + ZOMBIE_STILL_SPRITE_OFFSET) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
      Stance::NormalDeath if drawable.orientation != Orientation::Still => {
        let sprite_idx = (drawable.direction as usize * 6 + drawable.zombie_death_idx + NORMAL_DEATH_SPRITE_OFFSET) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
      Stance::CriticalDeath if drawable.orientation != Orientation::Still => {
        let sprite_idx = (drawable.direction as usize * 8 + drawable.zombie_death_idx) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
      _ => {
        drawable.direction = drawable.orientation;
        let sprite_idx = (drawable.orientation as usize * 8 + drawable.zombie_idx + ZOMBIE_STILL_SPRITE_OFFSET) as usize;
        (&self.data[sprite_idx], sprite_idx)
      },
    };

    let (y_div, row_idx) =
      if drawable.stance == Stance::NormalDeath || drawable.stance == Stance::CriticalDeath {
        (0.0, 2)
      } else {
        (1.0, 2)
      };

    let elements_x = ZOMBIE_SHEET_TOTAL_WIDTH / (zombie_sprite.0.data[2] + SPRITE_OFFSET);
    CharacterSheet {
      x_div: elements_x,
      y_div,
      row_idx,
      index: zombie_sprite.1 as f32,
    }
  }

  pub fn draw<C>(&mut self,
                 mut drawable: &mut ZombieDrawable,
                 encoder: &mut gfx::Encoder<R, C>)
                 where C: gfx::CommandBuffer<R> {
    encoder.update_constant_buffer(&self.bundle.data.projection_cb, &drawable.projection);
    encoder.update_constant_buffer(&self.bundle.data.position_cb, &drawable.position);
    encoder.update_constant_buffer(&self.bundle.data.character_sprite_cb,
                                   &self.get_next_sprite(&mut drawable));
    self.bundle.encode(encoder);
  }
}

#[derive(Debug)]
pub struct PreDrawSystem;

impl PreDrawSystem {
  pub fn new() -> PreDrawSystem {
    PreDrawSystem {}
  }
}

impl<'a> specs::prelude::System<'a> for PreDrawSystem {
  #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
  type SystemData = (WriteStorage<'a, Zombies>,
                     ReadStorage<'a, CameraInputState>,
                     ReadStorage<'a, CharacterInputState>,
                     ReadStorage<'a, Bullets>,
                     Read<'a, Dimensions>);

  fn run(&mut self, (mut zombies, camera_input, character_input, bullets, dim): Self::SystemData) {
    use specs::join::Join;

    for (zs, camera, ci, bs) in (&mut zombies, &camera_input, &character_input, &bullets).join() {
      let world_to_clip = dim.world_to_projection(camera);

      for z in &mut zs.zombies {
        z.update(&world_to_clip, ci);
        z.check_bullet_hits(&bs.bullets);
      }
    }
  }
}
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2022 Ryan Fowler
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// copies of the Software, and to permit persons to whom the Software is
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use clap::Parser;
use num_format::{Locale, ToFormattedString};
use serde::Serialize;
use std::path::PathBuf;
use std::str::FromStr;
use std::string::ToString;
use tabled::{
    object::{Columns, Rows},
    style::Border,
    Alignment, Modify, Style, Table, Tabled,
};

use crate::lang;

#[derive(Debug)]
pub enum Format {
    Json,
    Table,
}

impl FromStr for Format {
    type Err = String;
    fn from_str(format: &str) -> Result<Self, Self::Err> {
        match format {
            "json" => Ok(Format::Json),
            "table" => Ok(Format::Table),
            _ => Err(format.to_string()),
        }
    }
}

/// Count lines of code.
#[derive(Debug, Parser)]
#[clap(version, about)]
pub struct Args {
    /// Output format ("table" or "json").
    #[clap(short = 'o', long = "output", default_value = "table")]
    pub format: Format,

    /// Show timing information.
    #[clap(short, long)]
    pub timing: bool,

    /// Directory or file to scan.
    #[clap(default_value = ".")]
    pub path: PathBuf,
}

#[derive(Debug, Serialize)]
pub struct Output {
    pub languages: Vec<LangOut>,
    pub total_num_files: u64,
    pub total_num_lines: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub elapsed_ms: Option<u64>,
}

#[derive(Debug, Serialize)]
pub struct LangOut {
    pub language: lang::Language,
    pub num_files: u64,
    pub num_lines: u64,
}

pub fn get_options() -> Args {
    Args::parse()
}

pub fn write_output(out: &Output, format: Format) {
    match format {
        Format::Json => write_json_pretty(out),
        Format::Table => write_table(out),
    }
}

fn write_json_pretty(out: &Output) {
    println!("{}", serde_json::to_string_pretty(&out).unwrap());
}

#[derive(Tabled)]
struct Row {
    #[tabled(rename = "Language")]
    language: String,
    #[tabled(rename = "Files")]
    files: String,
    #[tabled(rename = "Lines")]
    lines: String,
}

fn write_table(out: &Output) {
    let mut data = Vec::new();
    for lang in &out.languages {
        data.push(Row {
            language: lang.language.as_str().to_string(),
            files: lang.num_files.to_formatted_string(&Locale::en),
            lines: lang.num_lines.to_formatted_string(&Locale::en),
        });
    }

    if out.languages.len() != 1 {
        data.push(Row {
            language: "Total".to_string(),
            files: out.total_num_files.to_formatted_string(&Locale::en),
            lines: out.total_num_lines.to_formatted_string(&Locale::en),
        });
    }

    let mut table = Table::new(&data)
        .with(Style::psql())
        .with(Modify::new(Columns::first()).with(Alignment::left()))
        .with(Modify::new(Columns::new(1..=2)).with(Alignment::right()))
        .with(Modify::new(Rows::first()).with(Alignment::left()));

    if out.languages.len() != 1 {
        table = table.with(Modify::new(Rows::last()).with(Border::default().top('-')));
    }

    println!("{}", table);

    if let Some(elapsed_ms) = out.elapsed_ms {
        println!("Took: {}ms", elapsed_ms);
    }
}
<commit_msg>Fix timing formatting<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2022 Ryan Fowler
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// copies of the Software, and to permit persons to whom the Software is
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use clap::Parser;
use num_format::{Locale, ToFormattedString};
use serde::Serialize;
use std::path::PathBuf;
use std::str::FromStr;
use std::string::ToString;
use tabled::{
    object::{Columns, Rows},
    style::Border,
    Alignment, Modify, Style, Table, Tabled,
};

use crate::lang;

#[derive(Debug)]
pub enum Format {
    Json,
    Table,
}

impl FromStr for Format {
    type Err = String;
    fn from_str(format: &str) -> Result<Self, Self::Err> {
        match format {
            "json" => Ok(Format::Json),
            "table" => Ok(Format::Table),
            _ => Err(format.to_string()),
        }
    }
}

/// Count lines of code.
#[derive(Debug, Parser)]
#[clap(version, about)]
pub struct Args {
    /// Output format ("table" or "json").
    #[clap(short = 'o', long = "output", default_value = "table")]
    pub format: Format,

    /// Show timing information.
    #[clap(short, long)]
    pub timing: bool,

    /// Directory or file to scan.
    #[clap(default_value = ".")]
    pub path: PathBuf,
}

#[derive(Debug, Serialize)]
pub struct Output {
    pub languages: Vec<LangOut>,
    pub total_num_files: u64,
    pub total_num_lines: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub elapsed_ms: Option<u64>,
}

#[derive(Debug, Serialize)]
pub struct LangOut {
    pub language: lang::Language,
    pub num_files: u64,
    pub num_lines: u64,
}

pub fn get_options() -> Args {
    Args::parse()
}

pub fn write_output(out: &Output, format: Format) {
    match format {
        Format::Json => write_json_pretty(out),
        Format::Table => write_table(out),
    }
}

fn write_json_pretty(out: &Output) {
    println!("{}", serde_json::to_string_pretty(&out).unwrap());
}

#[derive(Tabled)]
struct Row {
    #[tabled(rename = "Language")]
    language: String,
    #[tabled(rename = "Files")]
    files: String,
    #[tabled(rename = "Lines")]
    lines: String,
}

fn write_table(out: &Output) {
    let mut data = Vec::new();
    for lang in &out.languages {
        data.push(Row {
            language: lang.language.as_str().to_string(),
            files: lang.num_files.to_formatted_string(&Locale::en),
            lines: lang.num_lines.to_formatted_string(&Locale::en),
        });
    }

    if out.languages.len() != 1 {
        data.push(Row {
            language: "Total".to_string(),
            files: out.total_num_files.to_formatted_string(&Locale::en),
            lines: out.total_num_lines.to_formatted_string(&Locale::en),
        });
    }

    let mut table = Table::new(&data)
        .with(Style::psql())
        .with(Modify::new(Columns::first()).with(Alignment::left()))
        .with(Modify::new(Columns::new(1..=2)).with(Alignment::right()))
        .with(Modify::new(Rows::first()).with(Alignment::left()));

    if out.languages.len() != 1 {
        table = table.with(Modify::new(Rows::last()).with(Border::default().top('-')));
    }

    println!("{}", table);

    if let Some(elapsed_ms) = out.elapsed_ms {
        println!("\nTook: {}ms", elapsed_ms);
    }
}
<|endoftext|>