big refactor
- moved display logic into their own module for less cluttering - made abstractions for the display process so it can be owned by the game manager and game may be played without displays. - separated the main game management from the game struct makes it easier to clone games and play few turns on the fly.
This commit is contained in:
parent
b5a38cff72
commit
32cf3d280b
6 changed files with 327 additions and 262 deletions
|
@ -1,16 +1,13 @@
|
||||||
use std::{error::Error, fmt::Display, thread, time::Duration};
|
use std::{error::Error, fmt::Display};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
controller::{Controller, ControllerError, Move},
|
controller::{ControllerError, Move},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Rules {
|
pub struct Rules {
|
||||||
size: usize,
|
size: usize,
|
||||||
spawn_per_turn: usize,
|
spawn_per_turn: usize,
|
||||||
clear_term: bool,
|
|
||||||
color_seed: u16,
|
|
||||||
pause: Duration,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rules {
|
impl Rules {
|
||||||
|
@ -23,21 +20,6 @@ impl Rules {
|
||||||
self.spawn_per_turn = spawn_per_turn;
|
self.spawn_per_turn = spawn_per_turn;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_term(mut self, clear_term: bool) -> Self {
|
|
||||||
self.clear_term = clear_term;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn color_seed(mut self, color_seed: u16) -> Self {
|
|
||||||
self.color_seed = color_seed;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pause(mut self, pause: Duration) -> Self {
|
|
||||||
self.pause = pause;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Rules {
|
impl Default for Rules {
|
||||||
|
@ -45,9 +27,6 @@ impl Default for Rules {
|
||||||
Self {
|
Self {
|
||||||
size: 4,
|
size: 4,
|
||||||
spawn_per_turn: 1,
|
spawn_per_turn: 1,
|
||||||
clear_term: true,
|
|
||||||
color_seed: 35,
|
|
||||||
pause: Duration::ZERO,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,8 +58,6 @@ impl Error for GameError {}
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
board: Grid,
|
board: Grid,
|
||||||
spawn_per_turn: usize,
|
spawn_per_turn: usize,
|
||||||
clear_term: bool,
|
|
||||||
pause: Duration,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
|
@ -88,21 +65,16 @@ impl Game {
|
||||||
let Rules {
|
let Rules {
|
||||||
size,
|
size,
|
||||||
spawn_per_turn,
|
spawn_per_turn,
|
||||||
clear_term,
|
|
||||||
color_seed,
|
|
||||||
pause,
|
|
||||||
} = rules;
|
} = rules;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
board: Grid::new(size, color_seed),
|
board: Grid::new(size),
|
||||||
spawn_per_turn,
|
spawn_per_turn,
|
||||||
clear_term,
|
|
||||||
pause,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn controlled(self, controller: Box<dyn Controller>) -> ControlledGame {
|
pub fn get_board(&self) -> &Grid {
|
||||||
ControlledGame::new(self, controller, true)
|
&self.board
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn turn(&mut self, movement: Move) -> Result<(), GameError> {
|
pub fn turn(&mut self, movement: Move) -> Result<(), GameError> {
|
||||||
|
@ -110,11 +82,10 @@ impl Game {
|
||||||
for _ in 0..self.spawn_per_turn {
|
for _ in 0..self.spawn_per_turn {
|
||||||
self.spawn_random()?;
|
self.spawn_random()?;
|
||||||
}
|
}
|
||||||
thread::sleep(self.pause);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_random(&mut self) -> Result<(), GameError> {
|
fn spawn_random(&mut self) -> Result<(), GameError> {
|
||||||
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() {
|
||||||
|
@ -134,14 +105,6 @@ impl Game {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_display(&self) {
|
|
||||||
if self.clear_term {
|
|
||||||
super::clear_term();
|
|
||||||
}
|
|
||||||
let text = self.board.display();
|
|
||||||
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 {
|
||||||
|
@ -261,28 +224,3 @@ fn would_overflow(number: usize, delta: isize, max: usize) -> bool {
|
||||||
let too_big = number == max && delta == 1;
|
let too_big = number == max && delta == 1;
|
||||||
too_little || too_big
|
too_little || too_big
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ControlledGame {
|
|
||||||
game: Game,
|
|
||||||
controller: Box<dyn Controller>,
|
|
||||||
display: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ControlledGame {
|
|
||||||
pub fn new(game: Game, controller: Box<dyn Controller>, display: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
game,
|
|
||||||
controller,
|
|
||||||
display,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn turn(&mut self) -> Result<(), GameError> {
|
|
||||||
if self.display {
|
|
||||||
self.game.refresh_display();
|
|
||||||
}
|
|
||||||
let movement = self.controller.next_move(&self.game.board)?;
|
|
||||||
self.game.turn(movement)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
114
src/lib/game_manager.rs
Normal file
114
src/lib/game_manager.rs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
use std::{thread, time::Duration};
|
||||||
|
|
||||||
|
use crate::lib::{
|
||||||
|
controller::Controller,
|
||||||
|
game::{self, Game, GameError},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::grid_displayer::GridDisplayer;
|
||||||
|
|
||||||
|
pub struct Rules {
|
||||||
|
display: bool,
|
||||||
|
display_skips: usize,
|
||||||
|
clear_term: bool,
|
||||||
|
color_seed: u16,
|
||||||
|
turn_duration: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rules {
|
||||||
|
pub fn clear_term(mut self, clear_term: bool) -> Self {
|
||||||
|
self.clear_term = clear_term;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color_seed(mut self, color_seed: u16) -> Self {
|
||||||
|
self.color_seed = color_seed;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
||||||
|
if self.display {
|
||||||
|
if self.display_to_skip == 0 {
|
||||||
|
self.refresh_display();
|
||||||
|
self.display_to_skip = self.display_skips;
|
||||||
|
} else {
|
||||||
|
self.display_to_skip -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let movement = self.controller.next_move(self.game.get_board())?;
|
||||||
|
self.game.turn(movement)?;
|
||||||
|
thread::sleep(self.turn_duration);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_display(&self) {
|
||||||
|
if self.clear_term {
|
||||||
|
super::clear_term();
|
||||||
|
}
|
||||||
|
let grid = self.game.get_board();
|
||||||
|
let text = self.grid_displayer.display(grid);
|
||||||
|
println!("{text}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_all(&mut self) -> Result<(), GameError> {
|
||||||
|
loop {
|
||||||
|
self.turn()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
192
src/lib/grid.rs
192
src/lib/grid.rs
|
@ -1,11 +1,3 @@
|
||||||
use std::{
|
|
||||||
collections::hash_map::DefaultHasher,
|
|
||||||
hash::{Hash, Hasher},
|
|
||||||
mem::transmute_copy,
|
|
||||||
};
|
|
||||||
|
|
||||||
use termion::color;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Tile {
|
pub struct Tile {
|
||||||
value: Option<usize>,
|
value: Option<usize>,
|
||||||
|
@ -32,111 +24,21 @@ impl Tile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// displayability
|
|
||||||
///
|
|
||||||
impl Tile {
|
|
||||||
const TILE_LENGTH: usize = 7;
|
|
||||||
const TILE_HEIGHT: usize = 3;
|
|
||||||
|
|
||||||
pub fn display(&self, color_seed: u16) -> String {
|
|
||||||
match self.value {
|
|
||||||
Some(value) => {
|
|
||||||
Self::color_representation(Self::display_number(value), value, 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Grid {
|
pub struct Grid {
|
||||||
size: usize,
|
size: usize,
|
||||||
tiles: Vec<Vec<Tile>>,
|
tiles: Vec<Vec<Tile>>,
|
||||||
color_seed: u16,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
///
|
///
|
||||||
/// constructor
|
/// constructor
|
||||||
///
|
///
|
||||||
pub fn new(size: usize, color_seed: u16) -> 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 {
|
Self { size, tiles }
|
||||||
size,
|
|
||||||
tiles,
|
|
||||||
color_seed,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -177,6 +79,13 @@ impl Grid {
|
||||||
self.size
|
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
|
/// move a tile over another one, replace the previously occupied place by an empty tile and overrides the destination
|
||||||
///
|
///
|
||||||
|
@ -186,86 +95,3 @@ impl Grid {
|
||||||
self.tiles[src_y][src_x] = Tile::new_empty();
|
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] =
|
|
||||||
["┘", "┐", "┌", "└", "┼", "─", "├", "┤", "┴", "┬", "│"];
|
|
||||||
|
|
||||||
///
|
|
||||||
/// returns a string of multiple lines representing the grid
|
|
||||||
///
|
|
||||||
pub fn display(&self) -> String {
|
|
||||||
let tiles: Vec<Vec<_>> = self
|
|
||||||
.tiles
|
|
||||||
.iter()
|
|
||||||
.map(|row| {
|
|
||||||
row.iter()
|
|
||||||
.map(|tile| tile.display(self.color_seed))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let row_representations: Vec<_> = tiles
|
|
||||||
.iter()
|
|
||||||
.map(|row_representation| {
|
|
||||||
let mut row_lines = (0..Tile::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(),
|
|
||||||
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::<Vec<_>>()
|
|
||||||
.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::<Vec<_>>()
|
|
||||||
.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::<Vec<_>>()
|
|
||||||
.join(Self::DISPLAY_CHAR[8]);
|
|
||||||
[Self::DISPLAY_CHAR[3], &middle, Self::DISPLAY_CHAR[0], "\n"].join("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
188
src/lib/grid_displayer.rs
Normal file
188
src/lib/grid_displayer.rs
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
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("")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
pub mod controller;
|
pub mod controller;
|
||||||
pub mod game;
|
pub mod game;
|
||||||
|
pub mod game_manager;
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
|
pub mod grid_displayer;
|
||||||
|
|
||||||
pub fn clear_term() {
|
pub fn clear_term() {
|
||||||
print!("\x1B[2J\x1B[1;1H");
|
print!("\x1B[2J\x1B[1;1H");
|
||||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -1,18 +1,15 @@
|
||||||
use lib::{
|
use lib::{
|
||||||
controller::{player::PlayerController, Controller},
|
controller::{player::PlayerController, random::RandomController, Controller},
|
||||||
game::{Game, Rules},
|
game::{self, GameError},
|
||||||
|
game_manager::{self, GameManager},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod lib;
|
pub mod lib;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), GameError> {
|
||||||
let rules = Rules::default()
|
let game_rules = game::Rules::default().size(8).spawn_per_turn(1);
|
||||||
.size(4)
|
let manager_rules = game_manager::Rules::default();
|
||||||
.spawn_per_turn(1)
|
|
||||||
.clear_term(false);
|
|
||||||
let controller = PlayerController::new().into_box();
|
let controller = PlayerController::new().into_box();
|
||||||
let mut game = Game::new(rules).controlled(controller);
|
let mut managed = GameManager::new(game_rules, manager_rules, controller);
|
||||||
loop {
|
managed.play_all()
|
||||||
game.turn().unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue