split components into a separate crate, switched to workspace
This commit is contained in:
parent
951a1aa4d4
commit
316d760b58
23 changed files with 935 additions and 60 deletions
96
Cargo.lock
generated
96
Cargo.lock
generated
|
@ -33,16 +33,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.1.8"
|
version = "3.2.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
|
checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
"clap_lex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lazy_static",
|
"once_cell",
|
||||||
"os_str_bytes",
|
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
|
@ -50,9 +50,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "3.1.7"
|
version = "3.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
|
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
|
@ -62,10 +62,19 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "clap_lex"
|
||||||
version = "0.2.6"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||||
|
dependencies = [
|
||||||
|
"os_str_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -74,9 +83,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.11.2"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
|
@ -95,31 +104,19 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.8.1"
|
version = "1.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
|
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.121"
|
version = "0.2.132"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
|
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numtoa"
|
name = "numtoa"
|
||||||
|
@ -128,13 +125,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_str_bytes"
|
name = "once_cell"
|
||||||
version = "6.0.0"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
[[package]]
|
||||||
]
|
name = "os_str_bytes"
|
||||||
|
version = "6.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
|
@ -168,18 +168,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.36"
|
version = "1.0.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.17"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
|
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -216,9 +216,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.13"
|
version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
@ -249,13 +249,13 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.90"
|
version = "1.0.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f"
|
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-xid",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -286,10 +286,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-ident"
|
||||||
version = "0.2.2"
|
version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
|
@ -299,9 +299,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.2+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -1,12 +1,2 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "rs48"
|
members = ["rs48"]
|
||||||
version = "1.0.0"
|
|
||||||
edition = "2021"
|
|
||||||
description = "A game of 2048 that plays in the terminal as a TUI with a lot of configurability."
|
|
||||||
license = "GPL-3.0"
|
|
||||||
authors = ["JOLIMAITRE Matthieu <matthieu@imagevo.fr>"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
rand = "0.8.5"
|
|
||||||
termion = "1.5.6"
|
|
||||||
clap = { version = "3.1.8", features = ["derive"] }
|
|
12
rs48/Cargo.toml
Normal file
12
rs48/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "rs48"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "A game of 2048 that plays in the terminal as a TUI with a lot of configurability."
|
||||||
|
license = "GPL-3.0"
|
||||||
|
authors = ["JOLIMAITRE Matthieu <matthieu@imagevo.fr>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.8.5"
|
||||||
|
termion = "1.5.6"
|
||||||
|
clap = { version = "3.1.8", features = ["derive"] }
|
11
rs48_lib/Cargo.toml
Normal file
11
rs48_lib/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "rs48_lib"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "components of rs48"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["JOLIMAITRE Matthieu <matthieu@imagevo.fr>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.8"
|
||||||
|
termion = "1.5"
|
58
rs48_lib/src/controller.rs
Normal file
58
rs48_lib/src/controller.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
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;
|
32
rs48_lib/src/controller/player.rs
Normal file
32
rs48_lib/src/controller/player.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
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!()
|
||||||
|
}
|
||||||
|
}
|
19
rs48_lib/src/controller/random.rs
Normal file
19
rs48_lib/src/controller/random.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
34
rs48_lib/src/controller/simulated.rs
Normal file
34
rs48_lib/src/controller/simulated.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
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!()
|
||||||
|
}
|
||||||
|
}
|
251
rs48_lib/src/game.rs
Normal file
251
rs48_lib/src/game.rs
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
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
|
||||||
|
}
|
148
rs48_lib/src/game_manager.rs
Normal file
148
rs48_lib/src/game_manager.rs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
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()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
113
rs48_lib/src/grid.rs
Normal file
113
rs48_lib/src/grid.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
#[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)
|
||||||
|
}
|
||||||
|
}
|
188
rs48_lib/src/grid_displayer.rs
Normal file
188
rs48_lib/src/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("")
|
||||||
|
}
|
||||||
|
}
|
19
rs48_lib/src/lib.rs
Normal file
19
rs48_lib/src/lib.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue