changed standard to hard tabulations

This commit is contained in:
JOLIMAITRE Matthieu 2022-04-04 15:05:36 +03:00
parent 0b7e0f847c
commit 5fde51e81b
6 changed files with 411 additions and 410 deletions

1
rustfmt.toml Normal file
View file

@ -0,0 +1 @@
hard_tabs = true

View file

@ -2,63 +2,63 @@ use termion::{event::Key, input::TermRead, raw::IntoRawMode};
use super::grid::Grid; use super::grid::Grid;
use std::{ use std::{
error::Error, error::Error,
fmt::Display, fmt::Display,
io::{stdin, stdout}, io::{stdin, stdout},
}; };
pub enum Move { pub enum Move {
LEFT, LEFT,
RIGHT, RIGHT,
UP, UP,
DOWN, DOWN,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ControllerError { pub enum ControllerError {
ExitSignal, ExitSignal,
} }
impl Display for ControllerError { impl Display for ControllerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = match self { let msg = match self {
ControllerError::ExitSignal => "received exit signal", ControllerError::ExitSignal => "received exit signal",
}; };
f.write_str(msg) f.write_str(msg)
} }
} }
impl Error for ControllerError {} impl Error for ControllerError {}
pub trait Controller { pub trait Controller {
fn next_move(&mut self, grid: &Grid) -> Result<Move, ControllerError>; fn next_move(&mut self, grid: &Grid) -> Result<Move, ControllerError>;
} }
pub struct PlayerController { pub struct PlayerController {
// //
} }
impl PlayerController { impl PlayerController {
pub fn new() -> Self { pub fn new() -> Self {
Self {} Self {}
} }
} }
impl Controller for PlayerController { impl Controller for PlayerController {
fn next_move(&mut self, _grid: &Grid) -> Result<Move, ControllerError> { fn next_move(&mut self, _grid: &Grid) -> Result<Move, ControllerError> {
let stdin = stdin(); let stdin = stdin();
let mut _stdout = stdout().into_raw_mode().unwrap(); let mut _stdout = stdout().into_raw_mode().unwrap();
for c in stdin.keys() { for c in stdin.keys() {
let movement = match c.unwrap() { let movement = match c.unwrap() {
Key::Char('q') => return Err(ControllerError::ExitSignal), Key::Char('q') => return Err(ControllerError::ExitSignal),
Key::Left => Move::LEFT, Key::Left => Move::LEFT,
Key::Right => Move::RIGHT, Key::Right => Move::RIGHT,
Key::Up => Move::UP, Key::Up => Move::UP,
Key::Down => Move::DOWN, Key::Down => Move::DOWN,
_ => continue, _ => continue,
}; };
return Ok(movement); return Ok(movement);
} }
unreachable!() unreachable!()
} }
} }

View file

@ -1,226 +1,226 @@
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use super::{ use super::{
controller::{Controller, Move, PlayerController}, controller::{Controller, Move, PlayerController},
grid::Grid, grid::Grid,
}; };
pub struct Rules { pub struct Rules {
size: usize, size: usize,
spawn_per_turn: usize, spawn_per_turn: usize,
controller: Box<dyn Controller>, controller: Box<dyn Controller>,
} }
impl Rules { impl Rules {
pub fn size(mut self, size: usize) -> Self { pub fn size(mut self, size: usize) -> Self {
self.size = size; self.size = size;
self self
} }
pub fn spawn_per_turn(mut self, spawn_per_turn: usize) -> Self { pub fn spawn_per_turn(mut self, spawn_per_turn: usize) -> Self {
self.spawn_per_turn = spawn_per_turn; self.spawn_per_turn = spawn_per_turn;
self self
} }
} }
impl Default for Rules { impl Default for Rules {
fn default() -> Self { fn default() -> Self {
Self { Self {
size: 4, size: 4,
spawn_per_turn: 1, spawn_per_turn: 1,
controller: Box::new(PlayerController::new()), controller: Box::new(PlayerController::new()),
} }
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Err2048 { pub enum Err2048 {
GridIsFull, GridIsFull,
} }
impl Display for Err2048 { impl Display for Err2048 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = match self { let msg = match self {
&Self::GridIsFull => "grid is full", &Self::GridIsFull => "grid is full",
}; };
f.write_str(msg) f.write_str(msg)
} }
} }
impl Error for Err2048 {} impl Error for Err2048 {}
pub struct Game { pub struct Game {
board: Grid, board: Grid,
controller: Box<dyn Controller>, controller: Box<dyn Controller>,
spawn_per_turn: usize, spawn_per_turn: usize,
} }
impl Game { impl Game {
pub fn new(rules: Rules) -> Self { pub fn new(rules: Rules) -> Self {
let Rules { let Rules {
controller, controller,
size, size,
spawn_per_turn, spawn_per_turn,
} = rules; } = rules;
Self { Self {
board: Grid::new(size), board: Grid::new(size),
controller, controller,
spawn_per_turn, spawn_per_turn,
} }
} }
pub fn turn(&mut self) -> Result<(), Box<dyn Error>> { pub fn turn(&mut self) -> Result<(), Box<dyn Error>> {
for _ in 0..self.spawn_per_turn { for _ in 0..self.spawn_per_turn {
self.spawn_random()?; self.spawn_random()?;
} }
self.refresh_display(); self.refresh_display();
let movement = self.controller.next_move(&self.board)?; let movement = self.controller.next_move(&self.board)?;
self.perform_move(movement); self.perform_move(movement);
Ok(()) Ok(())
} }
pub fn spawn_random(&mut self) -> Result<(), Box<dyn Error>> { pub fn spawn_random(&mut self) -> Result<(), Box<dyn Error>> {
let mut potentials = vec![]; let mut potentials = vec![];
for x in 0..self.board.size() { for x in 0..self.board.size() {
for y in 0..self.board.size() { for y in 0..self.board.size() {
if self.board.get((x, y)).unwrap().is_empty() { if self.board.get((x, y)).unwrap().is_empty() {
potentials.push((x, y)); potentials.push((x, y));
} }
} }
} }
let potential_count = potentials.len() as f32; let potential_count = potentials.len() as f32;
if potential_count == 0. { if potential_count == 0. {
return Err(Err2048::GridIsFull.into()); return Err(Err2048::GridIsFull.into());
} }
let random = rand::random::<f32>() * potential_count; let random = rand::random::<f32>() * potential_count;
let index = random.floor() as usize; let index = random.floor() as usize;
let (x, y) = potentials[index]; let (x, y) = potentials[index];
self.board.set((x, y), Some(1)); self.board.set((x, y), Some(1));
Ok(()) Ok(())
} }
pub fn refresh_display(&self) { pub fn refresh_display(&self) {
super::clear_term(); super::clear_term();
let text = self.board.display(); let text = self.board.display();
println!("{text}"); println!("{text}");
} }
// TODO: macro peut être ? // TODO: macro peut être ?
pub fn perform_move(&mut self, movement: Move) { pub fn perform_move(&mut self, movement: Move) {
match movement { match movement {
Move::LEFT => { Move::LEFT => {
for y in 0..self.board.size() { for y in 0..self.board.size() {
for x in 0..self.board.size() { for x in 0..self.board.size() {
if !self.board.get((x, y)).unwrap().is_empty() { if !self.board.get((x, y)).unwrap().is_empty() {
self.perform_linear_move((-1, 0), (x, y)); self.perform_linear_move((-1, 0), (x, y));
} }
} }
} }
} }
Move::RIGHT => { Move::RIGHT => {
for y in 0..self.board.size() { for y in 0..self.board.size() {
for x in (0..self.board.size()).rev() { for x in (0..self.board.size()).rev() {
if !self.board.get((x, y)).unwrap().is_empty() { if !self.board.get((x, y)).unwrap().is_empty() {
self.perform_linear_move((1, 0), (x, y)); self.perform_linear_move((1, 0), (x, y));
} }
} }
} }
} }
Move::UP => { Move::UP => {
for x in 0..self.board.size() { for x in 0..self.board.size() {
for y in 0..self.board.size() { for y in 0..self.board.size() {
if !self.board.get((x, y)).unwrap().is_empty() { if !self.board.get((x, y)).unwrap().is_empty() {
self.perform_linear_move((0, -1), (x, y)); self.perform_linear_move((0, -1), (x, y));
} }
} }
} }
} }
Move::DOWN => { Move::DOWN => {
for x in 0..self.board.size() { for x in 0..self.board.size() {
for y in (0..self.board.size()).rev() { for y in (0..self.board.size()).rev() {
if !self.board.get((x, y)).unwrap().is_empty() { if !self.board.get((x, y)).unwrap().is_empty() {
self.perform_linear_move((0, 1), (x, y)); self.perform_linear_move((0, 1), (x, y));
} }
} }
} }
} }
}; };
} }
fn perform_linear_move(&mut self, direction: (isize, isize), tile_pos: (usize, usize)) { fn perform_linear_move(&mut self, direction: (isize, isize), tile_pos: (usize, usize)) {
let mut displacement = Displacement::new(&mut self.board, tile_pos, direction); let mut displacement = Displacement::new(&mut self.board, tile_pos, direction);
displacement.move_all(); displacement.move_all();
} }
} }
pub struct Displacement<'g> { pub struct Displacement<'g> {
grid: &'g mut Grid, grid: &'g mut Grid,
position: (usize, usize), position: (usize, usize),
direction: (isize, isize), direction: (isize, isize),
} }
impl<'g> Displacement<'g> { impl<'g> Displacement<'g> {
pub fn new(grid: &'g mut Grid, position: (usize, usize), direction: (isize, isize)) -> Self { pub fn new(grid: &'g mut Grid, position: (usize, usize), direction: (isize, isize)) -> Self {
Self { Self {
grid, grid,
position, position,
direction, direction,
} }
} }
pub fn move_all(&mut self) { pub fn move_all(&mut self) {
loop { loop {
let can_continue = self.move_once(); let can_continue = self.move_once();
if !can_continue { if !can_continue {
break; break;
} }
} }
} }
fn move_once(&mut self) -> bool { fn move_once(&mut self) -> bool {
let current_pos = self.position.clone(); let current_pos = self.position.clone();
let current_value = self.grid.get_val(current_pos).unwrap(); let current_value = self.grid.get_val(current_pos).unwrap();
if let Some(next_pos) = self.get_next_pos() { if let Some(next_pos) = self.get_next_pos() {
match self.grid.get_val(next_pos) { match self.grid.get_val(next_pos) {
None => { None => {
self.grid.move_tile(current_pos, next_pos); self.grid.move_tile(current_pos, next_pos);
self.set_pos(next_pos); self.set_pos(next_pos);
true true
} }
Some(value) if value == current_value => { Some(value) if value == current_value => {
self.grid.move_tile(current_pos, next_pos); self.grid.move_tile(current_pos, next_pos);
self.grid.set(next_pos, Some(value * 2)); self.grid.set(next_pos, Some(value * 2));
false false
} }
Some(_) => false, Some(_) => false,
} }
} else { } else {
false false
} }
} }
fn get_next_pos(&self) -> Option<(usize, usize)> { fn get_next_pos(&self) -> Option<(usize, usize)> {
let (current_x, current_y) = self.position.clone(); let (current_x, current_y) = self.position.clone();
let (dx, dy) = self.direction.clone(); let (dx, dy) = self.direction.clone();
if would_overflow(current_x, dx, self.grid.size() - 1) if would_overflow(current_x, dx, self.grid.size() - 1)
|| would_overflow(current_y, dy, self.grid.size() - 1) || would_overflow(current_y, dy, self.grid.size() - 1)
{ {
None None
} else { } else {
let next_x = (current_x as isize) + dx; let next_x = (current_x as isize) + dx;
let next_y = (current_y as isize) + dy; let next_y = (current_y as isize) + dy;
Some((next_x as usize, next_y as usize)) Some((next_x as usize, next_y as usize))
} }
} }
fn set_pos(&mut self, (x, y): (usize, usize)) { fn set_pos(&mut self, (x, y): (usize, usize)) {
self.position = (x, y); self.position = (x, y);
} }
} }
fn would_overflow(n: usize, d: isize, max: usize) -> bool { fn would_overflow(n: usize, d: isize, max: usize) -> bool {
let too_little = n == 0 && d == -1; let too_little = n == 0 && d == -1;
let too_big = n == max && d == 1; let too_big = n == max && d == 1;
too_little || too_big too_little || too_big
} }

View file

@ -1,231 +1,231 @@
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Tile { pub struct Tile {
value: Option<usize>, value: Option<usize>,
} }
impl Tile { impl Tile {
pub fn new_with_value(value: usize) -> Self { pub fn new_with_value(value: usize) -> Self {
Self { value: Some(value) } Self { value: Some(value) }
} }
pub fn new_empty() -> Self { pub fn new_empty() -> Self {
Self { value: None } Self { value: None }
} }
pub fn value(&self) -> Option<usize> { pub fn value(&self) -> Option<usize> {
self.value.clone() self.value.clone()
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
if let Some(_) = self.value { if let Some(_) = self.value {
false false
} else { } else {
true true
} }
} }
} }
/// ///
/// displayability /// displayability
/// ///
impl Tile { impl Tile {
const TILE_LENGTH: usize = 7; const TILE_LENGTH: usize = 7;
const TILE_HEIGHT: usize = 3; const TILE_HEIGHT: usize = 3;
pub fn display(&self) -> String { pub fn display(&self) -> String {
match self.value { match self.value {
Some(value) => Self::display_number(value), Some(value) => Self::display_number(value),
None => [ None => [
// empty tile // empty tile
" ", " ", " ", " ", " ", " ",
] ]
.join("\n"), .join("\n"),
} }
} }
pub fn display_number(value: usize) -> String { pub fn display_number(value: usize) -> String {
let result = [ let result = [
// number tile // number tile
"┌─────┐", "┌─────┐",
&Self::pad_both(value.to_string(), Self::TILE_LENGTH), &Self::pad_both(value.to_string(), Self::TILE_LENGTH),
"└─────┘", "└─────┘",
] ]
.join("\n"); .join("\n");
result result
} }
fn pad_both(text: String, length: usize) -> String { fn pad_both(text: String, length: usize) -> String {
let mut text = text; let mut text = text;
while text.len() < length { while text.len() < length {
text = format!(" {text} "); text = format!(" {text} ");
} }
if text.len() > length { if text.len() > length {
(&text)[..length].to_string() (&text)[..length].to_string()
} else { } else {
text text
} }
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Grid { pub struct Grid {
size: usize, size: usize,
tiles: Vec<Vec<Tile>>, tiles: Vec<Vec<Tile>>,
} }
impl Grid { impl Grid {
/// ///
/// constructor /// constructor
/// ///
pub fn new(size: usize) -> Self { pub fn new(size: usize) -> Self {
let tiles = (0..size) let tiles = (0..size)
.map(|_| (0..size).map(|_| Tile::new_empty()).collect()) .map(|_| (0..size).map(|_| Tile::new_empty()).collect())
.collect(); .collect();
Self { size, tiles } Self { size, tiles }
} }
/// ///
/// set the value of the tile at the selected position /// set the value of the tile at the selected position
/// ///
pub fn set(&mut self, (x, y): (usize, usize), value: Option<usize>) { pub fn set(&mut self, (x, y): (usize, usize), value: Option<usize>) {
self.tiles[y][x] = if let Some(value) = value { self.tiles[y][x] = if let Some(value) = value {
Tile::new_with_value(value) Tile::new_with_value(value)
} else { } else {
Tile::new_empty() Tile::new_empty()
}; };
} }
/// ///
/// get a tile if the position is in the grid /// get a tile if the position is in the grid
/// ///
pub fn get(&self, (x, y): (usize, usize)) -> Option<&Tile> { pub fn get(&self, (x, y): (usize, usize)) -> Option<&Tile> {
match self.tiles.get(y).map(|row| row.get(x)) { match self.tiles.get(y).map(|row| row.get(x)) {
Some(Some(tile)) => Some(tile), Some(Some(tile)) => Some(tile),
_ => None, _ => None,
} }
} }
/// ///
/// get the value of a tile if the position is in the grid and the tile has a value /// 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<usize> { pub fn get_val(&self, (x, y): (usize, usize)) -> Option<usize> {
match self.get((x, y)).map(|tile| tile.value()) { match self.get((x, y)).map(|tile| tile.value()) {
Some(Some(value)) => Some(value), Some(Some(value)) => Some(value),
_ => None, _ => None,
} }
} }
/// ///
/// get the size of the grid /// get the size of the grid
/// ///
pub fn size(&self) -> usize { pub fn size(&self) -> usize {
self.size self.size
} }
/// ///
/// move a tile over another one, replace the previously occupied place by an empty tile and overrides the destination /// 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)) { 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(); let src = self.tiles[src_y][src_x].clone();
self.tiles[dst_y][dst_x] = src; self.tiles[dst_y][dst_x] = src;
self.tiles[src_y][src_x] = Tile::new_empty(); self.tiles[src_y][src_x] = Tile::new_empty();
} }
} }
/// ///
/// displayability /// displayability
/// ///
impl Grid { impl Grid {
/// 0: '┘' /// 0: '┘'
/// ///
/// 1: '┐' /// 1: '┐'
/// ///
/// 2: '┌' /// 2: '┌'
/// ///
/// 3: '└' /// 3: '└'
/// ///
/// 4: '┼' /// 4: '┼'
/// ///
/// 5: '─' /// 5: '─'
/// ///
/// 6: '├' /// 6: '├'
/// ///
/// 7: '┤' /// 7: '┤'
/// ///
/// 8: '┴' /// 8: '┴'
/// ///
/// 9: '┬' /// 9: '┬'
/// ///
/// 10: '│' /// 10: '│'
const DISPLAY_CHAR: [&'static str; 11] = const DISPLAY_CHAR: [&'static str; 11] =
["", "", "", "", "", "", "", "", "", "", ""]; ["", "", "", "", "", "", "", "", "", "", ""];
/// ///
/// returns a string of multiple lines representing the grid /// returns a string of multiple lines representing the grid
/// ///
pub fn display(&self) -> String { pub fn display(&self) -> String {
let tiles: Vec<Vec<_>> = self let tiles: Vec<Vec<_>> = self
.tiles .tiles
.iter() .iter()
.map(|row| row.iter().map(|tile| tile.display()).collect()) .map(|row| row.iter().map(|tile| tile.display()).collect())
.collect(); .collect();
let row_representations: Vec<_> = tiles let row_representations: Vec<_> = tiles
.iter() .iter()
.map(|row_representation| { .map(|row_representation| {
let mut row_lines = (0..Tile::TILE_HEIGHT).map(|_| vec![]).collect::<Vec<_>>(); let mut row_lines = (0..Tile::TILE_HEIGHT).map(|_| vec![]).collect::<Vec<_>>();
// push every item lines in [`row_lines`] // push every item lines in [`row_lines`]
for item_representation in row_representation { for item_representation in row_representation {
item_representation item_representation
.split('\n') .split('\n')
.into_iter() .into_iter()
.zip(row_lines.iter_mut()) .zip(row_lines.iter_mut())
.for_each(|(item_line, row_line)| row_line.push(item_line.to_string())); .for_each(|(item_line, row_line)| row_line.push(item_line.to_string()));
} }
// join lines of [`row_lines`] // join lines of [`row_lines`]
let row_lines = row_lines let row_lines = row_lines
.iter_mut() .iter_mut()
.map(|line_parts| line_parts.join(Self::DISPLAY_CHAR[10]).to_string()) .map(|line_parts| line_parts.join(Self::DISPLAY_CHAR[10]).to_string())
.map(|line| [Self::DISPLAY_CHAR[10], &line, Self::DISPLAY_CHAR[10]].join("")) .map(|line| [Self::DISPLAY_CHAR[10], &line, Self::DISPLAY_CHAR[10]].join(""))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
row_lines.join("\n") row_lines.join("\n")
}) })
.collect(); .collect();
[ [
self.first_grid_display_line(), self.first_grid_display_line(),
row_representations.join(&self.between_grid_display_line()), row_representations.join(&self.between_grid_display_line()),
self.last_grid_display_line(), self.last_grid_display_line(),
] ]
.join("\n") .join("\n")
} }
fn first_grid_display_line(&self) -> String { fn first_grid_display_line(&self) -> String {
let middle = (0..self.size) let middle = (0..self.size)
.map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH)) .map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(Self::DISPLAY_CHAR[9]); .join(Self::DISPLAY_CHAR[9]);
[Self::DISPLAY_CHAR[2], &middle, Self::DISPLAY_CHAR[1]].join("") [Self::DISPLAY_CHAR[2], &middle, Self::DISPLAY_CHAR[1]].join("")
} }
fn between_grid_display_line(&self) -> String { fn between_grid_display_line(&self) -> String {
let middle = (0..self.size) let middle = (0..self.size)
.map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH)) .map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(Self::DISPLAY_CHAR[4]); .join(Self::DISPLAY_CHAR[4]);
[ [
"\n", "\n",
Self::DISPLAY_CHAR[6], Self::DISPLAY_CHAR[6],
&middle, &middle,
Self::DISPLAY_CHAR[7], Self::DISPLAY_CHAR[7],
"\n", "\n",
] ]
.join("") .join("")
} }
fn last_grid_display_line(&self) -> String { fn last_grid_display_line(&self) -> String {
let middle = (0..self.size) let middle = (0..self.size)
.map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH)) .map(|_| Self::DISPLAY_CHAR[5].repeat(Tile::TILE_LENGTH))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(Self::DISPLAY_CHAR[8]); .join(Self::DISPLAY_CHAR[8]);
[Self::DISPLAY_CHAR[3], &middle, Self::DISPLAY_CHAR[0], "\n"].join("") [Self::DISPLAY_CHAR[3], &middle, Self::DISPLAY_CHAR[0], "\n"].join("")
} }
} }

View file

@ -3,5 +3,5 @@ pub mod game;
pub mod grid; pub mod grid;
pub fn clear_term() { pub fn clear_term() {
print!("\x1B[2J\x1B[1;1H"); print!("\x1B[2J\x1B[1;1H");
} }

View file

@ -3,9 +3,9 @@ use lib::game::{Game, Rules};
pub mod lib; pub mod lib;
fn main() { fn main() {
let rules = Rules::default().size(4).spawn_per_turn(1); let rules = Rules::default().size(4).spawn_per_turn(1);
let mut game = Game::new(rules); let mut game = Game::new(rules);
loop { loop {
game.turn().unwrap(); game.turn().unwrap();
} }
} }