From 0a7ae9c234c03e8133be51474c9b97aff5a57202 Mon Sep 17 00:00:00 2001 From: Vegard Matthey Date: Thu, 25 Apr 2024 13:03:42 +0200 Subject: [PATCH] initial --- .gitignore | 2 + Cargo.toml | 15 +++ release.sh | 4 + src/main.rs | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100755 release.sh create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..754c45c --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..605a7a1 --- /dev/null +++ b/release.sh @@ -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 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e4a458f --- /dev/null +++ b/src/main.rs @@ -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 { + 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> = 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; + } + } + } + } + } + } + } + } +}