From 1a1df3f1613a5c8814c6eb6d3d91744cca63cf3b Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 12 Sep 2022 01:04:48 +0200 Subject: Initial commit --- src/display.rs | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/game.rs | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 31 ++++++++++++ 3 files changed, 327 insertions(+) create mode 100644 src/display.rs create mode 100644 src/game.rs create mode 100644 src/main.rs (limited to 'src') diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..6d750de --- /dev/null +++ b/src/display.rs @@ -0,0 +1,148 @@ +use super::game::{Board, Pos}; +use ansi_term::Color; +use std::fmt; + +enum Mode { + Roof_, + Data_, + Floor, + Empty, + Base_, +} + +const FIELD_HEIGHT: usize = 3; +const FIELD_WIDTH: usize = 8; + +fn write_line(f: &mut fmt::Formatter, vec: &[u32], mode: Mode) -> fmt::Result { + let mut vec = vec; + let len = vec.len(); + + write!( + f, + "{}", + match mode { + Mode::Roof_ => "┏", + Mode::Data_ => "┃", + Mode::Floor => "┠", + Mode::Empty => "┃", + Mode::Base_ => "┗", + } + )?; + + for i in 0..len { + let &n = vec.last().unwrap(); + vec = &vec[0..vec.len() - 1]; + + match mode { + Mode::Data_ | Mode::Empty => write!(f, "\x1b[0m ")?, + _ => {} + } + + let color = match mode { + Mode::Data_ | Mode::Empty if n != 0 => { + let (r, g, b) = hsl::HSL { + h: (n * 360 / 12) as f64, + s: 1.0, + l: 0.5, + } + .to_rgb(); + + Color::Black.on(Color::RGB(r, g, b)) + } + _ => Color::White.on(Color::Black), + }; + + if let Mode::Data_ = mode { + if n == 0 { + write!(f, "{}", " ".repeat(FIELD_WIDTH - 2))?; + } else { + write!( + f, + "{}", + color.paint(format!("{:^w$}", 1 << n, w = FIELD_WIDTH - 2)) + )?; + } + } else { + write!( + f, + "{}", + match mode { + Mode::Roof_ | Mode::Base_ => "━".repeat(FIELD_WIDTH), + Mode::Floor => "─".repeat(FIELD_WIDTH), + Mode::Empty => color.paint(" ".repeat(FIELD_WIDTH - 2)).to_string(), + Mode::Data_ => panic!("unreachable"), + } + )?; + } + + match mode { + Mode::Data_ | Mode::Empty => write!(f, " ")?, + _ => {} + } + + if i != len - 1 { + write!( + f, + "{}", + match mode { + Mode::Roof_ => "┯", + Mode::Data_ => "│", + Mode::Floor => "┼", + Mode::Empty => "│", + Mode::Base_ => "┷", + } + )?; + } + } + + write!( + f, + "{}", + match mode { + Mode::Roof_ => "┓", + Mode::Data_ => "┃", + Mode::Floor => "┨", + Mode::Empty => "┃", + Mode::Base_ => "┛", + } + )?; + + writeln!(f)?; + + Ok(()) +} + +impl fmt::Display for Board { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let dummy = vec![0; self.size.x as usize]; + + write_line(f, &dummy, Mode::Roof_)?; + + for y in 0..self.size.y { + let vec = (0..self.size.x) + .rev() + .map(|x| self.get(Pos::new(x, y)).value()) + .collect::>(); + + for i in 0..FIELD_HEIGHT { + write_line( + f, + &vec, + if i == FIELD_HEIGHT / 2 { + Mode::Data_ + } else { + Mode::Empty + }, + )?; + } + + if y != self.size.y - 1 { + write_line(f, &dummy, Mode::Floor)?; + } + } + + write_line(f, &dummy, Mode::Base_)?; + + Ok(()) + } +} diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..19940cd --- /dev/null +++ b/src/game.rs @@ -0,0 +1,148 @@ +pub use glam::i32::IVec2 as Pos; +use rand::seq::IteratorRandom; +use std::cell::RefCell; + +trait Swap { + fn swap(self) -> Self; +} + +impl Swap for Pos { + fn swap(self) -> Self { + Self::new(self.y, self.x) + } +} + +pub struct Field(RefCell); + +enum MergeResult { + Merged, + Replaced, + Blocked, + Empty, +} + +impl Field { + fn new() -> Self { + Self(RefCell::new(0)) + } + + pub fn value(&self) -> u32 { + *self.0.borrow() + } + + fn merge(&self, other: &Self) -> MergeResult { + let mut s = self.0.borrow_mut(); + let mut o = other.0.borrow_mut(); + + if *o == 0 { + return MergeResult::Empty; + } + + if *s == 0 { + *s = *o; + *o = 0; + + return MergeResult::Replaced; + } + + if *s == *o { + *s += 1; + *o = 0; + + return MergeResult::Merged; + } + + MergeResult::Blocked + } +} + +pub struct Board { + pub size: Pos, + fields: Vec>, +} + +pub enum Dir { + Up, + Down, + Left, + Right, +} + +impl Board { + pub fn new(size: Pos) -> Self { + Self { + size, + fields: (0..size.x) + .map(|_| (0..size.y).map(|_| Field::new()).collect()) + .collect(), + } + } + + pub fn step(&self, dir: Dir) -> bool { + let dir = match dir { + Dir::Up => -Pos::Y, + Dir::Down => Pos::Y, + Dir::Left => -Pos::X, + Dir::Right => Pos::X, + }; + + let step_row = dir.abs().swap(); + let step_col = -dir; + + let len_row = (step_row.abs() * self.size).max_element(); + let len_col = (step_col.abs() * self.size).max_element(); + + let start = (dir + Pos::ONE) / 2 * (self.size - Pos::ONE); + + let mut moved = false; + + for row in 0..len_row { + let start_row = start + row * step_row; + + for col1 in 0..len_col - 1 { + let field1 = self.get(start_row + col1 * step_col); + + for col2 in col1 + 1..len_col { + let field2 = self.get(start_row + col2 * step_col); + + match field1.merge(field2) { + MergeResult::Merged => { + moved = true; + break; + } + MergeResult::Replaced => { + moved = true; + } + MergeResult::Blocked => break, + MergeResult::Empty => {} + } + } + } + } + + moved + } + + pub fn get(&self, pos: Pos) -> &Field { + self.fields + .get(pos.x as usize) + .unwrap() + .get(pos.y as usize) + .unwrap() + } + + pub fn spawn(&self, rng: &mut R) + where + R: rand::Rng + ?core::marker::Sized, + { + if let Some(field) = self + .fields + .iter() + .flat_map(|v| v.iter()) + .filter(|f| f.value() == 0) + .choose(rng) + { + *field.0.borrow_mut() = 1; + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a7b3e59 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,31 @@ +pub mod display; +pub mod game; + +use game::{Board, Dir::*, Pos}; + +fn main() { + let mut rng = rand::thread_rng(); + let getch = getch::Getch::new(); + let board = Board::new(Pos::new(4, 4)); + + board.spawn(&mut rng); + clearscreen::clear().unwrap(); + print!("{board}"); + + while let Ok(ch) = getch.getch() { + if !board.step(match ch { + b'w' => Up, + b'a' => Left, + b's' => Down, + b'd' => Right, + b'q' => break, + _ => continue, + }) { + continue; + } + + board.spawn(&mut rng); + clearscreen::clear().unwrap(); + print!("{board}"); + } +} -- cgit v1.2.3