This commit is contained in:
2024-04-25 13:03:42 +02:00
commit 0a7ae9c234
4 changed files with 312 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "term_2048"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
[dependencies]
crossterm = "0.27.0"
rand = "0.8.5"
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
RUSTFLAGS="-Zlocation-detail=none" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --release
upx --best --lzma target/release/term_2048
cp target/release/term_2048 term_2048
+291
View File
@@ -0,0 +1,291 @@
use crossterm::{
event::{poll, read, Event, KeyCode},
terminal::{disable_raw_mode, enable_raw_mode},
};
use rand::prelude::*;
use std::{io, io::Write};
#[derive(Clone, Copy, PartialEq)]
enum Cell {
Empty,
Number(u32),
}
struct Board([[Cell; 4]; 4]);
enum Direction {
Up,
Down,
Right,
Left,
}
fn main() {
let mut board = Board::new();
let mut score = 0;
print!("\x1b[?25l");
loop {
board.spawn_cell().unwrap();
board.render(&score);
let copy = board.0.clone();
while copy == board.0 {
io::stdout().flush().unwrap();
enable_raw_mode().unwrap();
let direction = get_input().unwrap();
disable_raw_mode().unwrap();
board.move_cells(&direction);
board.merge_cells(&direction, &mut score);
board.move_cells(&direction);
}
}
}
fn get_input() -> Option<Direction> {
loop {
while poll(std::time::Duration::from_millis(16)).ok()? {
if let Event::Key(k) = read().ok()? {
if let KeyCode::Char(c) = k.code {
match c {
'h' => return Some(Direction::Left),
'l' => return Some(Direction::Right),
'k' => return Some(Direction::Up),
'j' => return Some(Direction::Down),
'q' => exit(),
_ => (),
};
}
}
}
}
}
fn exit() -> ! {
disable_raw_mode().unwrap();
print!("\x1b[?25h\x1b[2J\x1b[H");
std::process::exit(0);
}
impl Board {
fn new() -> Self {
Self([[Cell::Empty; 4]; 4])
}
fn render(&self, score: &u32) {
let mut s = String::new();
s.push_str("\x1b[1m\x1b[32mscore\x1b[m: ");
s.push_str(&score.to_string());
s.push('\n');
s.push('┏');
for _ in 0..3 {
s.push_str(&"".repeat(4));
s.push('┳');
}
s.push_str(&"".repeat(4));
s.push('┓');
s.push('\n');
for (i, line) in self.0.iter().enumerate() {
s.push_str("");
for cell in line {
match cell {
Cell::Empty => s.push_str(" "),
Cell::Number(n) => {
let n_s = n.to_string();
let len = n_s.len();
s.push_str(&(n_s + &" ".repeat(4 - len)))
}
}
s.push_str("");
}
if i < self.0.len() - 1 {
s.push('\n');
s.push('┣');
for _ in 0..3 {
s.push_str("━━━━");
s.push('╋');
}
s.push_str("━━━━");
s.push('┫');
s.push('\n');
} else {
s.push('\n');
s.push('┗');
for _ in 0..3 {
s.push_str("━━━━");
s.push('┻');
}
s.push_str("━━━━");
s.push('┛');
s.push('\n');
}
}
print!("\x1b[2J\x1b[H{}", s)
}
fn spawn_cell(&mut self) -> Option<()> {
let mut rng = rand::thread_rng();
let r = rng.gen_range(0..3);
let n = if r == 2 { 4 } else { 2 };
let mut empty_count = 0;
self.0.iter().for_each(|m| {
m.iter().for_each(|c| {
if let Cell::Empty = c {
empty_count += 1;
}
})
});
if empty_count == 0 {
return None;
}
let spawn = rng.gen_range(0..empty_count);
let mut index = 0;
let mut pos = None;
self.0.iter().enumerate().for_each(|(i, m)| {
m.iter().enumerate().for_each(|(j, c)| {
if let Cell::Empty = c {
if index == spawn {
pos = Some((j, i));
}
index += 1;
}
})
});
if let Some((x, y)) = pos {
self.0[y][x] = Cell::Number(n);
Some(())
} else {
None
}
}
fn move_cells(&mut self, direction: &Direction) {
let dyn_board: Vec<Vec<u32>> = self
.0
.iter()
.map(|m| {
m.iter()
.filter_map(|c| {
if let Cell::Number(n) = c {
Some(*n)
} else {
None
}
})
.collect()
})
.collect();
match direction {
Direction::Up => {
let copy = self.0.clone();
self.0 = [[Cell::Empty; 4]; 4];
let mut index = 0;
for i in (0..4).rev() {
for j in 0..4 {
if let Cell::Number(n) = copy[j][i] {
self.0[index][i] = Cell::Number(n);
index += 1;
}
}
index = 0;
}
}
Direction::Down => {
let copy = self.0.clone();
self.0 = [[Cell::Empty; 4]; 4];
let mut index = 0;
for i in 0..4 {
for j in 0..4 {
if let Cell::Number(n) = copy[3 - j][i] {
self.0[3 - index][i] = Cell::Number(n);
index += 1;
}
}
index = 0;
}
}
Direction::Right => {
self.0 = [[Cell::Empty; 4]; 4];
self.0.iter_mut().enumerate().for_each(|(i, m)| {
dyn_board[i].iter().rev().enumerate().for_each(|(j, n)| {
m[3 - j] = Cell::Number(*n);
})
});
}
Direction::Left => {
self.0 = [[Cell::Empty; 4]; 4];
self.0.iter_mut().enumerate().for_each(|(i, m)| {
dyn_board[i].iter().enumerate().for_each(|(j, n)| {
m[j] = Cell::Number(*n);
})
});
}
}
}
fn merge_cells(&mut self, direction: &Direction, score: &mut u32) {
match direction {
Direction::Up => {
for i in 0..4 {
for j in 0..3 {
if let Cell::Number(a) = self.0[j][i] {
if let Cell::Number(b) = self.0[j + 1][i] {
if a == b {
self.0[j][i] = Cell::Number(b * 2);
self.0[j + 1][i] = Cell::Empty;
*score += b * 2;
}
}
}
}
}
}
Direction::Down => {
for i in 0..4 {
for j in (0..3).rev() {
if let Cell::Number(a) = self.0[j][i] {
if let Cell::Number(b) = self.0[j + 1][i] {
if a == b {
self.0[j + 1][i] = Cell::Number(b * 2);
self.0[j][i] = Cell::Empty;
*score += b * 2;
}
}
}
}
}
}
Direction::Right => {
for line in self.0.iter_mut() {
for i in (0..3).rev() {
if let Cell::Number(a) = line[i] {
if let Cell::Number(b) = line[i + 1] {
if a == b {
line[i + 1] = Cell::Number(b * 2);
line[i] = Cell::Empty;
*score += b * 2;
}
}
}
}
}
}
Direction::Left => {
for line in self.0.iter_mut() {
for i in (1..4).rev() {
if let Cell::Number(a) = line[4 - i] {
if let Cell::Number(b) = line[3 - i] {
if a == b {
line[3 - i] = Cell::Number(b * 2);
line[4 - i] = Cell::Empty;
*score += b * 2;
}
}
}
}
}
}
}
}
}