]> git.lizzy.rs Git - rs2048.git/commitdiff
Initial commit
authorElias Fleckenstein <eliasfleckenstein@web.de>
Sun, 11 Sep 2022 23:04:48 +0000 (01:04 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Sun, 11 Sep 2022 23:04:48 +0000 (01:04 +0200)
.gitignore [new file with mode: 0644]
Cargo.lock [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
rust-toolchain.toml [new file with mode: 0644]
src/display.rs [new file with mode: 0644]
src/game.rs [new file with mode: 0644]
src/main.rs [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ea8c4bf
--- /dev/null
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644 (file)
index 0000000..e7b427c
--- /dev/null
@@ -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 (file)
index 0000000..a7862f7
--- /dev/null
@@ -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 (file)
index 0000000..ff3a1a1
--- /dev/null
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly-2022-07-24"
diff --git a/src/display.rs b/src/display.rs
new file mode 100644 (file)
index 0000000..6d750de
--- /dev/null
@@ -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 (file)
index 0000000..19940cd
--- /dev/null
@@ -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 (file)
index 0000000..a7b3e59
--- /dev/null
@@ -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}");
+    }
+}