switched to crate lib

This commit is contained in:
JOLIMAITRE Matthieu 2022-09-03 02:37:33 +02:00
parent 316d760b58
commit 5c5e6357ad
16 changed files with 15 additions and 869 deletions

9
Cargo.lock generated
View file

@ -237,6 +237,15 @@ name = "rs48"
version = "1.0.0"
dependencies = [
"clap",
"rand",
"rs48_lib",
"termion",
]
[[package]]
name = "rs48_lib"
version = "1.0.0"
dependencies = [
"rand",
"termion",
]

View file

@ -10,3 +10,4 @@ authors = ["JOLIMAITRE Matthieu <matthieu@imagevo.fr>"]
rand = "0.8.5"
termion = "1.5.6"
clap = { version = "3.1.8", features = ["derive"] }
rs48_lib = { path = "../rs48_lib" }

View file

@ -1,58 +0,0 @@
use rand::{distributions::Standard, prelude::Distribution};
use super::grid::Grid;
use std::{error::Error, fmt::Display};
#[derive(Debug)]
pub enum Move {
LEFT,
RIGHT,
UP,
DOWN,
}
impl Distribution<Move> for Standard {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Move {
match rng.gen_range(0..4) {
0 => Move::DOWN,
1 => Move::LEFT,
2 => Move::RIGHT,
_ => Move::UP,
}
}
}
#[derive(Debug)]
pub enum ControllerError {
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)
}
}
impl Error for ControllerError {}
pub trait Controller {
fn next_move(&mut self, grid: &Grid) -> Result<Move, ControllerError>;
fn into_box(self) -> Box<dyn Controller>
where
Self: Sized + 'static,
{
Box::new(self)
}
}
pub mod player;
pub mod random;
pub mod simulated;
pub use player::PlayerController;
pub use random::RandomController;
pub use simulated::SimulatedController;

View file

@ -1,32 +0,0 @@
use std::io::{stdin, stdout};
use termion::{event::Key, input::TermRead, raw::IntoRawMode};
use super::{Controller, ControllerError, Move};
use crate::lib::grid::Grid;
pub struct PlayerController;
impl PlayerController {
pub fn new() -> Self {
Self
}
}
impl Controller for PlayerController {
fn next_move(&mut self, _grid: &Grid) -> Result<Move, ControllerError> {
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!()
}
}

View file

@ -1,19 +0,0 @@
use rand::random;
use super::{Controller, ControllerError, Move};
use crate::lib::grid::Grid;
pub struct RandomController;
impl RandomController {
pub fn new() -> Self {
Self
}
}
impl Controller for RandomController {
fn next_move(&mut self, _grid: &Grid) -> Result<Move, ControllerError> {
let movement = random();
Ok(movement)
}
}

View file

@ -1,34 +0,0 @@
use crate::lib::grid::Grid;
use super::{Controller, ControllerError, Move};
pub enum Objective {
Score,
TileCount,
}
pub struct SimulatedController {
_simulations_per_move: usize,
_length_of_simulation: usize,
_objective: Objective,
}
impl SimulatedController {
pub fn new(
_simulations_per_move: usize,
_length_of_simulation: usize,
_objective: Objective,
) -> Self {
Self {
_simulations_per_move,
_length_of_simulation,
_objective,
}
}
}
impl Controller for SimulatedController {
fn next_move(&mut self, _grid: &Grid) -> Result<Move, ControllerError> {
todo!()
}
}

View file

@ -1,251 +0,0 @@
use std::{error::Error, fmt::Display};
use super::{
controller::{ControllerError, Move},
grid::Grid,
};
pub struct Rules {
size: usize,
spawn_per_turn: usize,
}
impl Rules {
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
}
}
impl Default for Rules {
fn default() -> Self {
Self {
size: 4,
spawn_per_turn: 1,
}
}
}
#[derive(Debug)]
pub enum GameError {
GridIsFull,
ControllerError(ControllerError),
}
impl From<ControllerError> for GameError {
fn from(error: ControllerError) -> Self {
Self::ControllerError(error)
}
}
impl Display for GameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::GridIsFull => f.write_str("grid is full"),
GameError::ControllerError(err) => err.fmt(f),
}
}
}
impl Error for GameError {}
#[derive(Clone)]
pub struct Game {
board: Grid,
score: usize,
turn_index: usize,
spawn_per_turn: usize,
}
impl Game {
pub fn new(rules: Rules) -> Self {
let Rules {
size,
spawn_per_turn,
} = rules;
Self {
board: Grid::new(size),
score: 0,
turn_index: 0,
spawn_per_turn,
}
}
pub fn get_board(&self) -> &Grid {
&self.board
}
pub fn get_score(&self) -> usize {
self.score
}
pub fn get_turn_index(&self) -> usize {
self.turn_index
}
pub fn turn(&mut self, movement: Move) -> Result<(), GameError> {
let move_score = self.perform_move(movement);
self.score += move_score;
for _ in 0..self.spawn_per_turn {
self.spawn_random()?;
}
self.turn_index += 1;
Ok(())
}
fn spawn_random(&mut self) -> Result<(), GameError> {
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(GameError::GridIsFull.into());
}
let random = rand::random::<f32>() * potential_count;
let index = random.floor() as usize;
let (x, y) = potentials[index];
self.board.set((x, y), Some(1));
Ok(())
}
// TODO: macro peut être ?
pub fn perform_move(&mut self, movement: Move) -> usize {
let mut move_score = 0;
match movement {
Move::LEFT => {
for y in 0..self.board.size() {
for x in 0..self.board.size() {
move_score += 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() {
move_score += self.perform_linear_move((1, 0), (x, y));
}
}
}
Move::UP => {
for x in 0..self.board.size() {
for y in 0..self.board.size() {
move_score += 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() {
move_score += self.perform_linear_move((0, 1), (x, y));
}
}
}
};
move_score
}
fn perform_linear_move(
&mut self,
direction: (isize, isize),
tile_pos: (usize, usize),
) -> usize {
if self.board.get(tile_pos.clone()).unwrap().is_empty() {
0
} else {
let mut displacement = Displacement::new(&mut self.board, tile_pos, direction);
displacement.move_all();
displacement.pop_score()
}
}
}
pub struct Displacement<'g> {
grid: &'g mut Grid,
position: (usize, usize),
direction: (isize, isize),
score: usize,
}
impl<'g> Displacement<'g> {
pub fn new(grid: &'g mut Grid, position: (usize, usize), direction: (isize, isize)) -> Self {
Self {
grid,
position,
direction,
score: 0,
}
}
pub fn pop_score(self) -> usize {
let Displacement { score, .. } = self;
score
}
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));
self.score = 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 set_pos(&mut self, (x, y): (usize, usize)) {
self.position = (x, y);
}
}
/// determine if the given number, added a delta that is either 1 or -1 to it, would overflow a certain maximum value for n
fn would_overflow(number: usize, delta: isize, max: usize) -> bool {
let too_little = number == 0 && delta == -1;
let too_big = number == max && delta == 1;
too_little || too_big
}

View file

@ -1,148 +0,0 @@
use std::{thread, time::Duration};
use crate::lib::{
controller::Controller,
game::{self, Game, GameError},
};
use super::{clear_term, grid_displayer::GridDisplayer};
pub struct Rules {
display: bool,
display_skips: usize,
clear_term: bool,
color_seed: u16,
turn_duration: Duration,
}
impl Rules {
/// wether to display the game at all or not
pub fn display(mut self, display: bool) -> Self {
self.display = display;
self
}
/// turns to skip the display of
pub fn display_skips(mut self, display_skips: usize) -> Self {
self.display_skips = display_skips;
self
}
/// wether to clear the terminal or not between displays
pub fn clear_term(mut self, clear_term: bool) -> Self {
self.clear_term = clear_term;
self
}
/// seed for the procedural coloration of tiles
pub fn color_seed(mut self, color_seed: u16) -> Self {
self.color_seed = color_seed;
self
}
/// duration of pauses between turns
pub fn turn_duration(mut self, turn_duration: Duration) -> Self {
self.turn_duration = turn_duration;
self
}
}
impl Default for Rules {
fn default() -> Self {
Self {
display: true,
display_skips: 0,
clear_term: true,
color_seed: 35,
turn_duration: Duration::ZERO,
}
}
}
pub struct GameManager {
game: Game,
controller: Box<dyn Controller>,
grid_displayer: GridDisplayer,
display_to_skip: usize,
display: bool,
display_skips: usize,
clear_term: bool,
turn_duration: Duration,
}
impl GameManager {
pub fn new(
game_rules: game::Rules,
manager_rules: self::Rules,
controller: Box<dyn Controller>,
) -> Self {
let game = Game::new(game_rules);
let Rules {
clear_term,
color_seed,
display,
display_skips,
turn_duration,
} = manager_rules;
let grid_displayer = GridDisplayer::new(color_seed);
Self {
game,
controller,
display_to_skip: 0,
display,
display_skips,
clear_term,
turn_duration,
grid_displayer,
}
}
pub fn turn(&mut self) -> Result<(), GameError> {
self.display_conditionnally();
self.game_turn()?;
thread::sleep(self.turn_duration);
Ok(())
}
fn display_conditionnally(&mut self) {
if self.display {
if self.display_to_skip == 0 {
if self.clear_term {
clear_term();
}
self.print_display();
self.display_to_skip = self.display_skips;
} else {
self.display_to_skip -= 1;
}
}
}
fn game_turn(&mut self) -> Result<(), GameError> {
let board = self.game.get_board();
let movement = self.controller.next_move(board)?;
self.game.turn(movement)?;
Ok(())
}
pub fn print_display(&self) {
let headline_display = self.get_headline_display();
println!("{headline_display}");
let grid = self.game.get_board();
let grid_display = self.grid_displayer.display(grid);
println!("{grid_display}");
}
fn get_headline_display(&self) -> String {
let score = self.game.get_score();
let turn = self.game.get_turn_index();
let biggest_tile = self.game.get_board().biggest_value();
format!("score: {score:>12} | biggest tile: {biggest_tile:>12} | turn: {turn:>12}")
}
pub fn play_all(&mut self) -> Result<(), GameError> {
loop {
self.turn()?;
}
}
}

View file

@ -1,113 +0,0 @@
#[derive(Clone, Copy)]
pub struct Tile {
value: Option<usize>,
}
impl Tile {
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<usize> {
self.value.clone()
}
pub fn is_empty(&self) -> bool {
if let Some(_) = self.value {
false
} else {
true
}
}
}
#[derive(Clone)]
pub struct Grid {
size: usize,
tiles: Vec<Vec<Tile>>,
}
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 }
}
///
/// set the value of the tile at the selected position
///
pub fn set(&mut self, (x, y): (usize, usize), value: Option<usize>) {
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 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> {
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 array of tiles
///
pub fn tiles(&self) -> &Vec<Vec<Tile>> {
&self.tiles
}
///
/// 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();
}
///
/// get the biggest value of the board
///
pub fn biggest_value(&self) -> usize {
self.tiles()
.iter()
.map(|row| {
row.iter()
.filter_map(|tile| tile.value())
.reduce(|a, b| a.max(b))
})
.filter_map(|value| value)
.reduce(|a, b| a.max(b))
.unwrap_or(0)
}
}

View file

@ -1,188 +0,0 @@
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
mem::transmute_copy,
};
use termion::color;
use super::grid::{Grid, Tile};
pub struct TileDisplayer {
color_seed: u16,
}
impl TileDisplayer {
pub fn new(color_seed: u16) -> Self {
Self { color_seed }
}
const TILE_LENGTH: usize = 7;
const TILE_HEIGHT: usize = 3;
pub fn display(&self, tile: &Tile) -> String {
match tile.value() {
Some(value) => {
Self::color_representation(Self::display_number(value), value, self.color_seed)
}
None => [
// empty tile
" ", " ", " ",
]
.join("\n"),
}
}
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 color_representation(text: String, value: usize, color_seed: u16) -> String {
let color = Self::hashed_color(value, color_seed);
let color_code = color::Bg(color);
let reset_code = color::Bg(color::Reset);
let text = text
.split("\n")
.map(|line| format!("{color_code}{line}{reset_code}"))
.collect::<Vec<_>>()
.join("\n");
text
}
fn hashed_color(value: usize, color_seed: u16) -> color::Rgb {
let mut hasher = DefaultHasher::new();
(value + color_seed as usize).hash(&mut hasher);
let hash = hasher.finish();
// SAFETY:
// there are no logic that relies on the value of the outputted numbers, thus it is safe to create them without constructors
let [frac_a, frac_b]: [f64; 2] = unsafe { transmute_copy::<_, [u32; 2]>(&hash) }
.into_iter()
.map(|frac| (frac as f64) / (u32::MAX as f64))
.collect::<Vec<_>>()
.try_into()
.unwrap();
let mut remaining = 255f64;
let r = Self::take_fraction(&mut remaining, frac_a, 150.) as u8;
let g = Self::take_fraction(&mut remaining, frac_b, 150.) as u8;
let b = remaining as u8;
color::Rgb(r, g, b)
}
fn take_fraction(remainder: &mut f64, frac: f64, max: f64) -> f64 {
let result = (*remainder * frac).min(max);
*remainder -= result;
result
}
}
pub struct GridDisplayer {
tile_displayer: TileDisplayer,
}
impl GridDisplayer {
pub fn new(color_seed: u16) -> Self {
let tile_displayer = TileDisplayer::new(color_seed);
Self { tile_displayer }
}
/// (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, grid: &Grid) -> String {
let tiles: Vec<Vec<_>> = grid
.tiles()
.iter()
.map(|row| {
row.iter()
.map(|tile| self.tile_displayer.display(tile))
.collect()
})
.collect();
let row_representations: Vec<_> = tiles
.iter()
.map(|row_representation| {
let mut row_lines = (0..TileDisplayer::TILE_HEIGHT)
.map(|_| vec![])
.collect::<Vec<_>>();
// 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::<Vec<_>>();
row_lines.join("\n")
})
.collect();
[
self.first_grid_display_line(grid),
row_representations.join(&self.between_grid_display_line(grid)),
self.last_grid_display_line(grid),
]
.join("\n")
}
fn first_grid_display_line(&self, grid: &Grid) -> String {
let middle = (0..grid.size())
.map(|_| Self::DISPLAY_CHAR[5].repeat(TileDisplayer::TILE_LENGTH))
.collect::<Vec<_>>()
.join(Self::DISPLAY_CHAR[9]);
[Self::DISPLAY_CHAR[2], &middle, Self::DISPLAY_CHAR[1]].join("")
}
fn between_grid_display_line(&self, grid: &Grid) -> String {
let middle = (0..grid.size())
.map(|_| Self::DISPLAY_CHAR[5].repeat(TileDisplayer::TILE_LENGTH))
.collect::<Vec<_>>()
.join(Self::DISPLAY_CHAR[4]);
[
"\n",
Self::DISPLAY_CHAR[6],
&middle,
Self::DISPLAY_CHAR[7],
"\n",
]
.join("")
}
fn last_grid_display_line(&self, grid: &Grid) -> String {
let middle = (0..grid.size())
.map(|_| Self::DISPLAY_CHAR[5].repeat(TileDisplayer::TILE_LENGTH))
.collect::<Vec<_>>()
.join(Self::DISPLAY_CHAR[8]);
[Self::DISPLAY_CHAR[3], &middle, Self::DISPLAY_CHAR[0], "\n"].join("")
}
}

View file

@ -1,19 +0,0 @@
pub mod controller;
pub mod game;
pub mod game_manager;
pub mod grid;
pub mod grid_displayer;
pub fn clear_term() {
print!("\x1B[2J\x1B[1;1H");
}
pub mod prelude {
pub use super::controller::{
Controller, PlayerController, RandomController, SimulatedController,
};
pub use super::game::GameError;
pub use super::game::Rules as GameRules;
pub use super::game_manager::GameManager;
pub use super::game_manager::Rules as ManagerRules;
}

View file

@ -1,9 +1,7 @@
pub mod lib;
use std::{fmt::Display, str::FromStr, time::Duration};
use clap::{ArgEnum, Parser};
use lib::prelude::*;
use rs48_lib::prelude::*;
#[derive(Clone, ArgEnum, Debug)]
pub enum ControllerParam {

View file

@ -2,7 +2,7 @@ use std::io::{stdin, stdout};
use termion::{event::Key, input::TermRead, raw::IntoRawMode};
use super::{Controller, ControllerError, Move};
use crate::lib::grid::Grid;
use crate::grid::Grid;
pub struct PlayerController;

View file

@ -1,7 +1,7 @@
use rand::random;
use super::{Controller, ControllerError, Move};
use crate::lib::grid::Grid;
use crate::grid::Grid;
pub struct RandomController;

View file

@ -1,4 +1,4 @@
use crate::lib::grid::Grid;
use crate::grid::Grid;
use super::{Controller, ControllerError, Move};

View file

@ -1,6 +1,6 @@
use std::{thread, time::Duration};
use crate::lib::{
use crate::{
controller::Controller,
game::{self, Game, GameError},
};