From 1a1df3f1613a5c8814c6eb6d3d91744cca63cf3b Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Mon, 12 Sep 2022 01:04:48 +0200 Subject: [PATCH 1/1] Initial commit --- .gitignore | 1 + Cargo.lock | 450 ++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 12 ++ rust-toolchain.toml | 2 + src/display.rs | 148 +++++++++++++++ src/game.rs | 148 +++++++++++++++ src/main.rs | 31 +++ 7 files changed, 792 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 rust-toolchain.toml create mode 100644 src/display.rs create mode 100644 src/game.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e7b427c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,450 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clearscreen" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c969a6b6dadff9f3349b1f783f553e2411104763ca4789e1c6ca6a41f46a57b0" +dependencies = [ + "nix", + "terminfo", + "thiserror", + "which", + "winapi", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13990e2d5b29e1770ddf7fc000afead4acb9bd8f8a9602de63bf189e261b1ba8" +dependencies = [ + "libc", + "termios", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "hsl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575fb7f1167f3b88ed825e90eb14918ac460461fdeaa3965c6a50951dee1c970" + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "once_cell" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.7", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "rs2048" +version = "0.1.0" +dependencies = [ + "ansi_term", + "clearscreen", + "getch", + "glam", + "hsl", + "rand 0.8.5", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminfo" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e" +dependencies = [ + "dirs", + "fnv", + "nom", + "phf", + "phf_codegen", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "thiserror" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a7862f7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rs2048" +version = "0.1.0" +edition = "2021" + +[dependencies] +ansi_term = "0.12.1" +clearscreen = "1.0.10" +getch = "0.3.1" +glam = "0.21.3" +hsl = "0.1.1" +rand = "0.8.5" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..ff3a1a1 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2022-07-24" 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}"); + } +} -- 2.44.0