diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..218e203 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/src/lib/controller.rs b/src/lib/controller.rs index 3f6eb15..64ab607 100644 --- a/src/lib/controller.rs +++ b/src/lib/controller.rs @@ -2,63 +2,63 @@ use termion::{event::Key, input::TermRead, raw::IntoRawMode}; use super::grid::Grid; use std::{ - error::Error, - fmt::Display, - io::{stdin, stdout}, + error::Error, + fmt::Display, + io::{stdin, stdout}, }; pub enum Move { - LEFT, - RIGHT, - UP, - DOWN, + LEFT, + RIGHT, + UP, + DOWN, } #[derive(Debug)] pub enum ControllerError { - ExitSignal, + ExitSignal, } impl Display for ControllerError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let msg = match self { - ControllerError::ExitSignal => "received exit signal", - }; - f.write_str(msg) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let msg = match self { + ControllerError::ExitSignal => "received exit signal", + }; + f.write_str(msg) + } } impl Error for ControllerError {} pub trait Controller { - fn next_move(&mut self, grid: &Grid) -> Result; + fn next_move(&mut self, grid: &Grid) -> Result; } pub struct PlayerController { - // + // } impl PlayerController { - pub fn new() -> Self { - Self {} - } + pub fn new() -> Self { + Self {} + } } impl Controller for PlayerController { - fn next_move(&mut self, _grid: &Grid) -> Result { - let stdin = stdin(); - let mut _stdout = stdout().into_raw_mode().unwrap(); - for c in stdin.keys() { - let movement = match c.unwrap() { - Key::Char('q') => return Err(ControllerError::ExitSignal), - Key::Left => Move::LEFT, - Key::Right => Move::RIGHT, - Key::Up => Move::UP, - Key::Down => Move::DOWN, - _ => continue, - }; - return Ok(movement); - } - unreachable!() - } + fn next_move(&mut self, _grid: &Grid) -> Result { + let stdin = stdin(); + let mut _stdout = stdout().into_raw_mode().unwrap(); + for c in stdin.keys() { + let movement = match c.unwrap() { + Key::Char('q') => return Err(ControllerError::ExitSignal), + Key::Left => Move::LEFT, + Key::Right => Move::RIGHT, + Key::Up => Move::UP, + Key::Down => Move::DOWN, + _ => continue, + }; + return Ok(movement); + } + unreachable!() + } } diff --git a/src/lib/game.rs b/src/lib/game.rs index 58dea96..90ee947 100644 --- a/src/lib/game.rs +++ b/src/lib/game.rs @@ -1,226 +1,226 @@ use std::{error::Error, fmt::Display}; use super::{ - controller::{Controller, Move, PlayerController}, - grid::Grid, + controller::{Controller, Move, PlayerController}, + grid::Grid, }; pub struct Rules { - size: usize, - spawn_per_turn: usize, - controller: Box, + size: usize, + spawn_per_turn: usize, + controller: Box, } impl Rules { - pub fn size(mut self, size: usize) -> Self { - self.size = size; - self - } + pub fn size(mut self, size: usize) -> Self { + self.size = size; + self + } - pub fn spawn_per_turn(mut self, spawn_per_turn: usize) -> Self { - self.spawn_per_turn = spawn_per_turn; - self - } + pub fn spawn_per_turn(mut self, spawn_per_turn: usize) -> Self { + self.spawn_per_turn = spawn_per_turn; + self + } } impl Default for Rules { - fn default() -> Self { - Self { - size: 4, - spawn_per_turn: 1, - controller: Box::new(PlayerController::new()), - } - } + fn default() -> Self { + Self { + size: 4, + spawn_per_turn: 1, + controller: Box::new(PlayerController::new()), + } + } } #[derive(Debug)] pub enum Err2048 { - GridIsFull, + GridIsFull, } impl Display for Err2048 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let msg = match self { - &Self::GridIsFull => "grid is full", - }; - f.write_str(msg) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let msg = match self { + &Self::GridIsFull => "grid is full", + }; + f.write_str(msg) + } } impl Error for Err2048 {} pub struct Game { - board: Grid, - controller: Box, - spawn_per_turn: usize, + board: Grid, + controller: Box, + spawn_per_turn: usize, } impl Game { - pub fn new(rules: Rules) -> Self { - let Rules { - controller, - size, - spawn_per_turn, - } = rules; + pub fn new(rules: Rules) -> Self { + let Rules { + controller, + size, + spawn_per_turn, + } = rules; - Self { - board: Grid::new(size), - controller, - spawn_per_turn, - } - } + Self { + board: Grid::new(size), + controller, + spawn_per_turn, + } + } - pub fn turn(&mut self) -> Result<(), Box> { - for _ in 0..self.spawn_per_turn { - self.spawn_random()?; - } - self.refresh_display(); - let movement = self.controller.next_move(&self.board)?; - self.perform_move(movement); - Ok(()) - } + pub fn turn(&mut self) -> Result<(), Box> { + for _ in 0..self.spawn_per_turn { + self.spawn_random()?; + } + self.refresh_display(); + let movement = self.controller.next_move(&self.board)?; + self.perform_move(movement); + Ok(()) + } - pub fn spawn_random(&mut self) -> Result<(), Box> { - let mut potentials = vec![]; - for x in 0..self.board.size() { - for y in 0..self.board.size() { - if self.board.get((x, y)).unwrap().is_empty() { - potentials.push((x, y)); - } - } - } - let potential_count = potentials.len() as f32; - if potential_count == 0. { - return Err(Err2048::GridIsFull.into()); - } - let random = rand::random::() * potential_count; - let index = random.floor() as usize; - let (x, y) = potentials[index]; - self.board.set((x, y), Some(1)); - Ok(()) - } + pub fn spawn_random(&mut self) -> Result<(), Box> { + let mut potentials = vec![]; + for x in 0..self.board.size() { + for y in 0..self.board.size() { + if self.board.get((x, y)).unwrap().is_empty() { + potentials.push((x, y)); + } + } + } + let potential_count = potentials.len() as f32; + if potential_count == 0. { + return Err(Err2048::GridIsFull.into()); + } + let random = rand::random::() * potential_count; + let index = random.floor() as usize; + let (x, y) = potentials[index]; + self.board.set((x, y), Some(1)); + Ok(()) + } - pub fn refresh_display(&self) { - super::clear_term(); - let text = self.board.display(); - println!("{text}"); - } + pub fn refresh_display(&self) { + super::clear_term(); + let text = self.board.display(); + println!("{text}"); + } - // TODO: macro peut être ? - pub fn perform_move(&mut self, movement: Move) { - match movement { - Move::LEFT => { - for y in 0..self.board.size() { - for x in 0..self.board.size() { - if !self.board.get((x, y)).unwrap().is_empty() { - self.perform_linear_move((-1, 0), (x, y)); - } - } - } - } - Move::RIGHT => { - for y in 0..self.board.size() { - for x in (0..self.board.size()).rev() { - if !self.board.get((x, y)).unwrap().is_empty() { - self.perform_linear_move((1, 0), (x, y)); - } - } - } - } - Move::UP => { - for x in 0..self.board.size() { - for y in 0..self.board.size() { - if !self.board.get((x, y)).unwrap().is_empty() { - self.perform_linear_move((0, -1), (x, y)); - } - } - } - } - Move::DOWN => { - for x in 0..self.board.size() { - for y in (0..self.board.size()).rev() { - if !self.board.get((x, y)).unwrap().is_empty() { - self.perform_linear_move((0, 1), (x, y)); - } - } - } - } - }; - } + // TODO: macro peut être ? + pub fn perform_move(&mut self, movement: Move) { + match movement { + Move::LEFT => { + for y in 0..self.board.size() { + for x in 0..self.board.size() { + if !self.board.get((x, y)).unwrap().is_empty() { + self.perform_linear_move((-1, 0), (x, y)); + } + } + } + } + Move::RIGHT => { + for y in 0..self.board.size() { + for x in (0..self.board.size()).rev() { + if !self.board.get((x, y)).unwrap().is_empty() { + self.perform_linear_move((1, 0), (x, y)); + } + } + } + } + Move::UP => { + for x in 0..self.board.size() { + for y in 0..self.board.size() { + if !self.board.get((x, y)).unwrap().is_empty() { + self.perform_linear_move((0, -1), (x, y)); + } + } + } + } + Move::DOWN => { + for x in 0..self.board.size() { + for y in (0..self.board.size()).rev() { + if !self.board.get((x, y)).unwrap().is_empty() { + self.perform_linear_move((0, 1), (x, y)); + } + } + } + } + }; + } - fn perform_linear_move(&mut self, direction: (isize, isize), tile_pos: (usize, usize)) { - let mut displacement = Displacement::new(&mut self.board, tile_pos, direction); - displacement.move_all(); - } + fn perform_linear_move(&mut self, direction: (isize, isize), tile_pos: (usize, usize)) { + let mut displacement = Displacement::new(&mut self.board, tile_pos, direction); + displacement.move_all(); + } } pub struct Displacement<'g> { - grid: &'g mut Grid, - position: (usize, usize), - direction: (isize, isize), + grid: &'g mut Grid, + position: (usize, usize), + direction: (isize, isize), } impl<'g> Displacement<'g> { - pub fn new(grid: &'g mut Grid, position: (usize, usize), direction: (isize, isize)) -> Self { - Self { - grid, - position, - direction, - } - } + pub fn new(grid: &'g mut Grid, position: (usize, usize), direction: (isize, isize)) -> Self { + Self { + grid, + position, + direction, + } + } - pub fn move_all(&mut self) { - loop { - let can_continue = self.move_once(); - if !can_continue { - break; - } - } - } + pub fn move_all(&mut self) { + loop { + let can_continue = self.move_once(); + if !can_continue { + break; + } + } + } - fn move_once(&mut self) -> bool { - let current_pos = self.position.clone(); - let current_value = self.grid.get_val(current_pos).unwrap(); - if let Some(next_pos) = self.get_next_pos() { - match self.grid.get_val(next_pos) { - None => { - self.grid.move_tile(current_pos, next_pos); - self.set_pos(next_pos); - true - } - Some(value) if value == current_value => { - self.grid.move_tile(current_pos, next_pos); - self.grid.set(next_pos, Some(value * 2)); - false - } - Some(_) => false, - } - } else { - false - } - } + fn move_once(&mut self) -> bool { + let current_pos = self.position.clone(); + let current_value = self.grid.get_val(current_pos).unwrap(); + if let Some(next_pos) = self.get_next_pos() { + match self.grid.get_val(next_pos) { + None => { + self.grid.move_tile(current_pos, next_pos); + self.set_pos(next_pos); + true + } + Some(value) if value == current_value => { + self.grid.move_tile(current_pos, next_pos); + self.grid.set(next_pos, Some(value * 2)); + false + } + Some(_) => false, + } + } else { + false + } + } - fn get_next_pos(&self) -> Option<(usize, usize)> { - let (current_x, current_y) = self.position.clone(); - let (dx, dy) = self.direction.clone(); - if would_overflow(current_x, dx, self.grid.size() - 1) - || would_overflow(current_y, dy, self.grid.size() - 1) - { - None - } else { - let next_x = (current_x as isize) + dx; - let next_y = (current_y as isize) + dy; - Some((next_x as usize, next_y as usize)) - } - } + fn get_next_pos(&self) -> Option<(usize, usize)> { + let (current_x, current_y) = self.position.clone(); + let (dx, dy) = self.direction.clone(); + if would_overflow(current_x, dx, self.grid.size() - 1) + || would_overflow(current_y, dy, self.grid.size() - 1) + { + None + } else { + let next_x = (current_x as isize) + dx; + let next_y = (current_y as isize) + dy; + Some((next_x as usize, next_y as usize)) + } + } - fn set_pos(&mut self, (x, y): (usize, usize)) { - self.position = (x, y); - } + fn set_pos(&mut self, (x, y): (usize, usize)) { + self.position = (x, y); + } } fn would_overflow(n: usize, d: isize, max: usize) -> bool { - let too_little = n == 0 && d == -1; - let too_big = n == max && d == 1; - too_little || too_big + let too_little = n == 0 && d == -1; + let too_big = n == max && d == 1; + too_little || too_big } diff --git a/src/lib/grid.rs b/src/lib/grid.rs index a48173a..3703e17 100644 --- a/src/lib/grid.rs +++ b/src/lib/grid.rs @@ -1,231 +1,231 @@ #[derive(Clone, Copy)] pub struct Tile { - value: Option, + value: Option, } impl Tile { - pub fn new_with_value(value: usize) -> Self { - Self { value: Some(value) } - } - pub fn new_empty() -> Self { - Self { value: None } - } + pub fn new_with_value(value: usize) -> Self { + Self { value: Some(value) } + } + pub fn new_empty() -> Self { + Self { value: None } + } - pub fn value(&self) -> Option { - self.value.clone() - } + pub fn value(&self) -> Option { + self.value.clone() + } - pub fn is_empty(&self) -> bool { - if let Some(_) = self.value { - false - } else { - true - } - } + pub fn is_empty(&self) -> bool { + if let Some(_) = self.value { + false + } else { + true + } + } } /// /// displayability /// impl Tile { - const TILE_LENGTH: usize = 7; - const TILE_HEIGHT: usize = 3; + const TILE_LENGTH: usize = 7; + const TILE_HEIGHT: usize = 3; - pub fn display(&self) -> String { - match self.value { - Some(value) => Self::display_number(value), - None => [ - // empty tile - " ", " ", " ", - ] - .join("\n"), - } - } + pub fn display(&self) -> String { + match self.value { + Some(value) => Self::display_number(value), + None => [ + // empty tile + " ", " ", " ", + ] + .join("\n"), + } + } - pub fn display_number(value: usize) -> String { - let result = [ - // number tile - "┌─────┐", - &Self::pad_both(value.to_string(), Self::TILE_LENGTH), - "└─────┘", - ] - .join("\n"); - result - } + pub fn display_number(value: usize) -> String { + let result = [ + // number tile + "┌─────┐", + &Self::pad_both(value.to_string(), Self::TILE_LENGTH), + "└─────┘", + ] + .join("\n"); + result + } - fn pad_both(text: String, length: usize) -> String { - let mut text = text; - while text.len() < length { - text = format!(" {text} "); - } - if text.len() > length { - (&text)[..length].to_string() - } else { - text - } - } + fn pad_both(text: String, length: usize) -> String { + let mut text = text; + while text.len() < length { + text = format!(" {text} "); + } + if text.len() > length { + (&text)[..length].to_string() + } else { + text + } + } } #[derive(Clone)] pub struct Grid { - size: usize, - tiles: Vec>, + size: usize, + tiles: Vec>, } impl Grid { - /// - /// constructor - /// - pub fn new(size: usize) -> Self { - let tiles = (0..size) - .map(|_| (0..size).map(|_| Tile::new_empty()).collect()) - .collect(); - Self { size, tiles } - } + /// + /// constructor + /// + pub fn new(size: usize) -> Self { + let tiles = (0..size) + .map(|_| (0..size).map(|_| Tile::new_empty()).collect()) + .collect(); + Self { size, tiles } + } - /// - /// set the value of the tile at the selected position - /// - pub fn set(&mut self, (x, y): (usize, usize), value: Option) { - self.tiles[y][x] = if let Some(value) = value { - Tile::new_with_value(value) - } else { - Tile::new_empty() - }; - } + /// + /// set the value of the tile at the selected position + /// + pub fn set(&mut self, (x, y): (usize, usize), value: Option) { + self.tiles[y][x] = if let Some(value) = value { + Tile::new_with_value(value) + } else { + Tile::new_empty() + }; + } - /// - /// get a tile if the position is in the grid - /// - pub fn get(&self, (x, y): (usize, usize)) -> Option<&Tile> { - match self.tiles.get(y).map(|row| row.get(x)) { - Some(Some(tile)) => Some(tile), - _ => None, - } - } + /// + /// get a tile if the position is in the grid + /// + pub fn get(&self, (x, y): (usize, usize)) -> Option<&Tile> { + match self.tiles.get(y).map(|row| row.get(x)) { + Some(Some(tile)) => Some(tile), + _ => None, + } + } - /// - /// get the value of a tile if the position is in the grid and the tile has a value - /// - pub fn get_val(&self, (x, y): (usize, usize)) -> Option { - match self.get((x, y)).map(|tile| tile.value()) { - Some(Some(value)) => Some(value), - _ => None, - } - } + /// + /// get the value of a tile if the position is in the grid and the tile has a value + /// + pub fn get_val(&self, (x, y): (usize, usize)) -> Option { + match self.get((x, y)).map(|tile| tile.value()) { + Some(Some(value)) => Some(value), + _ => None, + } + } - /// - /// get the size of the grid - /// - pub fn size(&self) -> usize { - self.size - } + /// + /// get the size of the grid + /// + pub fn size(&self) -> usize { + self.size + } - /// - /// move a tile over another one, replace the previously occupied place by an empty tile and overrides the destination - /// - pub fn move_tile(&mut self, (src_x, src_y): (usize, usize), (dst_x, dst_y): (usize, usize)) { - let src = self.tiles[src_y][src_x].clone(); - self.tiles[dst_y][dst_x] = src; - self.tiles[src_y][src_x] = Tile::new_empty(); - } + /// + /// move a tile over another one, replace the previously occupied place by an empty tile and overrides the destination + /// + pub fn move_tile(&mut self, (src_x, src_y): (usize, usize), (dst_x, dst_y): (usize, usize)) { + let src = self.tiles[src_y][src_x].clone(); + self.tiles[dst_y][dst_x] = src; + self.tiles[src_y][src_x] = Tile::new_empty(); + } } /// /// displayability /// impl Grid { - /// 0: '┘' - /// - /// 1: '┐' - /// - /// 2: '┌' - /// - /// 3: '└' - /// - /// 4: '┼' - /// - /// 5: '─' - /// - /// 6: '├' - /// - /// 7: '┤' - /// - /// 8: '┴' - /// - /// 9: '┬' - /// - /// 10: '│' - const DISPLAY_CHAR: [&'static str; 11] = - ["┘", "┐", "┌", "└", "┼", "─", "├", "┤", "┴", "┬", "│"]; + /// 0: '┘' + /// + /// 1: '┐' + /// + /// 2: '┌' + /// + /// 3: '└' + /// + /// 4: '┼' + /// + /// 5: '─' + /// + /// 6: '├' + /// + /// 7: '┤' + /// + /// 8: '┴' + /// + /// 9: '┬' + /// + /// 10: '│' + const DISPLAY_CHAR: [&'static str; 11] = + ["┘", "┐", "┌", "└", "┼", "─", "├", "┤", "┴", "┬", "│"]; - /// - /// returns a string of multiple lines representing the grid - /// - pub fn display(&self) -> String { - let tiles: Vec> = self - .tiles - .iter() - .map(|row| row.iter().map(|tile| tile.display()).collect()) - .collect(); - let row_representations: Vec<_> = tiles - .iter() - .map(|row_representation| { - let mut row_lines = (0..Tile::TILE_HEIGHT).map(|_| vec![]).collect::>(); - // push every item lines in [`row_lines`] - for item_representation in row_representation { - item_representation - .split('\n') - .into_iter() - .zip(row_lines.iter_mut()) - .for_each(|(item_line, row_line)| row_line.push(item_line.to_string())); - } - // join lines of [`row_lines`] - let row_lines = row_lines - .iter_mut() - .map(|line_parts| line_parts.join(Self::DISPLAY_CHAR[10]).to_string()) - .map(|line| [Self::DISPLAY_CHAR[10], &line, Self::DISPLAY_CHAR[10]].join("")) - .collect::>(); - row_lines.join("\n") - }) - .collect(); + /// + /// returns a string of multiple lines representing the grid + /// + pub fn display(&self) -> String { + let tiles: Vec> = self + .tiles + .iter() + .map(|row| row.iter().map(|tile| tile.display()).collect()) + .collect(); + let row_representations: Vec<_> = tiles + .iter() + .map(|row_representation| { + let mut row_lines = (0..Tile::TILE_HEIGHT).map(|_| vec![]).collect::>(); + // push every item lines in [`row_lines`] + for item_representation in row_representation { + item_representation + .split('\n') + .into_iter() + .zip(row_lines.iter_mut()) + .for_each(|(item_line, row_line)| row_line.push(item_line.to_string())); + } + // join lines of [`row_lines`] + let row_lines = row_lines + .iter_mut() + .map(|line_parts| line_parts.join(Self::DISPLAY_CHAR[10]).to_string()) + .map(|line| [Self::DISPLAY_CHAR[10], &line, Self::DISPLAY_CHAR[10]].join("")) + .collect::>(); + row_lines.join("\n") + }) + .collect(); - [ - self.first_grid_display_line(), - row_representations.join(&self.between_grid_display_line()), - self.last_grid_display_line(), - ] - .join("\n") - } + [ + self.first_grid_display_line(), + row_representations.join(&self.between_grid_display_line()), + self.last_grid_display_line(), + ] + .join("\n") + } - fn first_grid_display_line(&self) -> String { - let middle = (0..self.size) - .map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH)) - .collect::>() - .join(Self::DISPLAY_CHAR[9]); - [Self::DISPLAY_CHAR[2], &middle, Self::DISPLAY_CHAR[1]].join("") - } + fn first_grid_display_line(&self) -> String { + let middle = (0..self.size) + .map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH)) + .collect::>() + .join(Self::DISPLAY_CHAR[9]); + [Self::DISPLAY_CHAR[2], &middle, Self::DISPLAY_CHAR[1]].join("") + } - fn between_grid_display_line(&self) -> String { - let middle = (0..self.size) - .map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH)) - .collect::>() - .join(Self::DISPLAY_CHAR[4]); - [ - "\n", - Self::DISPLAY_CHAR[6], - &middle, - Self::DISPLAY_CHAR[7], - "\n", - ] - .join("") - } + fn between_grid_display_line(&self) -> String { + let middle = (0..self.size) + .map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH)) + .collect::>() + .join(Self::DISPLAY_CHAR[4]); + [ + "\n", + Self::DISPLAY_CHAR[6], + &middle, + Self::DISPLAY_CHAR[7], + "\n", + ] + .join("") + } - fn last_grid_display_line(&self) -> String { - let middle = (0..self.size) - .map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH)) - .collect::>() - .join(Self::DISPLAY_CHAR[8]); - [Self::DISPLAY_CHAR[3], &middle, Self::DISPLAY_CHAR[0], "\n"].join("") - } + fn last_grid_display_line(&self) -> String { + let middle = (0..self.size) + .map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH)) + .collect::>() + .join(Self::DISPLAY_CHAR[8]); + [Self::DISPLAY_CHAR[3], &middle, Self::DISPLAY_CHAR[0], "\n"].join("") + } } diff --git a/src/lib/mod.rs b/src/lib/mod.rs index 4efef85..dccb036 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -3,5 +3,5 @@ pub mod game; pub mod grid; pub fn clear_term() { - print!("\x1B[2J\x1B[1;1H"); + print!("\x1B[2J\x1B[1;1H"); } diff --git a/src/main.rs b/src/main.rs index 4b56d38..17eb2fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,9 @@ use lib::game::{Game, Rules}; pub mod lib; fn main() { - let rules = Rules::default().size(4).spawn_per_turn(1); - let mut game = Game::new(rules); - loop { - game.turn().unwrap(); - } + let rules = Rules::default().size(4).spawn_per_turn(1); + let mut game = Game::new(rules); + loop { + game.turn().unwrap(); + } }