summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorElias Fleckenstein <eliasfleckenstein@web.de>2022-09-12 01:04:48 +0200
committerElias Fleckenstein <eliasfleckenstein@web.de>2022-09-12 01:04:48 +0200
commit1a1df3f1613a5c8814c6eb6d3d91744cca63cf3b (patch)
tree584b02079796dcef7152fcdcb650804ce0feea62 /src
downloadrs2048-1a1df3f1613a5c8814c6eb6d3d91744cca63cf3b.tar.xz
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/display.rs148
-rw-r--r--src/game.rs148
-rw-r--r--src/main.rs31
3 files changed, 327 insertions, 0 deletions
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::<Vec<u32>>();
+
+ 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<u32>);
+
+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<Vec<Field>>,
+}
+
+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<R>(&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}");
+ }
+}