commit ea6093bd0ad1d27405667864bb07248b55bbdb0b Author: Manuel Friedli Date: Mon Jan 2 00:26:57 2023 +0100 Initial commit - Let max. 100 UFOs cross the screen. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..0a8642f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Zeppelin ignored files +/ZeppelinRemoteNotebooks/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..ca06b23 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,87 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/jpa-buddy.xml b/.idea/jpa-buddy.xml new file mode 100644 index 0000000..966d5f5 --- /dev/null +++ b/.idea/jpa-buddy.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d79bd4e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b2af643 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..f5f2744 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6e7d279 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,297 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +dependencies = [ + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rust-ufo" +version = "0.1.0" +dependencies = [ + "crossterm", + "rand", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "signal-hook" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0f742af --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rust-ufo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +crossterm = "0.25.0" +rand = "0.8.5" diff --git a/rust-ufo.iml b/rust-ufo.iml new file mode 100644 index 0000000..2fecef3 --- /dev/null +++ b/rust-ufo.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b92c0bb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,77 @@ +use std::thread; +use std::time::Duration; + +use rand::Rng; + +use crate::Direction::{Down, Left, Right, Up}; +use crate::movables::{Movable, Ufo}; + +mod movables; +mod terminal; + +const WIDTH: u16 = 160; +const HEIGHT: u16 = 40; +const HEADER_ROW: u16 = 0; +const FOOTER_ROW: u16 = HEIGHT - 1; +const TANK_ROW: u16 = FOOTER_ROW - 1; +const MIN_UFO_ROW: u16 = 1; +const MAX_UFO_ROW: u16 = TANK_ROW - 5; +const DELAY: Duration = Duration::from_millis(10); +const UFO_STR: &str = "<=000=>"; +const TANK_STR: &str = "⊆≡≣🠭≣≡⊇"; + + +fn main() { + let terminal = terminal::setup(); + const MAX_UFOS: u8 = 100; + let mut ufos = vec![Ufo::create(), Ufo::create(), Ufo::create(), Ufo::create(), Ufo::create()]; + let mut n_ufos = ufos.len() as u8; + loop { + let iter_mut = ufos.iter_mut(); + for x in iter_mut { + x.mov(Direction::Left); + x.draw(); + } + if rand::thread_rng().gen_bool(0.1) && n_ufos < MAX_UFOS { + ufos.push(Ufo::create()); + n_ufos += 1; + } + ufos.retain(|ufo| ufo.is_on_screen()); + if ufos.is_empty() { + break; + } + thread::sleep(DELAY); + } + // for _ in 0..N_UFOS { + // let mut ufo = Ufo::create(); + // while ufo.is_on_screen() { + // ufo.mov(Direction::Left); + // ufo.draw(); + // thread::sleep(DELAY); + // } + // } + + terminal::restore(terminal); +} + +fn tank_at(col: u16) { + terminal::print_str_at(col, TANK_ROW, TANK_STR); +} + +pub enum Direction { + Left, + Right, + Up, + Down, +} + +impl Direction { + fn invert(&self) -> Direction { + match self { + Left => Right, + Right => Left, + Up => Down, + Down => Up + } + } +} diff --git a/src/movables.rs b/src/movables.rs new file mode 100644 index 0000000..d54ae12 --- /dev/null +++ b/src/movables.rs @@ -0,0 +1,116 @@ +use rand::Rng; + +use crate::{Direction, MAX_UFO_ROW, MIN_UFO_ROW, TANK_STR, UFO_STR, WIDTH}; +use crate::Direction::{Down, Left, Right}; +use crate::terminal::print_str_at; + +pub trait Movable { + fn create() -> T; + fn mov(&mut self, direction: Direction); + fn draw(&self); + fn is_on_screen(&self) -> bool; +} + +impl Movable for Ufo { + fn create() -> Ufo { + let mut rng = rand::thread_rng(); + let direction: Direction = if rng.gen() { Left } else { Right }; + let row = rng.gen_range(MIN_UFO_ROW..=MAX_UFO_ROW); + let column = match direction { + // If the UFO is moving right, we will initially place it just at the left edge. + Right => -(UFO_STR.len() as i16) + 1, + // If the UFO is moving left, we will initially place it just at the right edge. + Left => WIDTH as i16 - 1, + // Above, we're creating either a Left or a Right direction value. So, we CANNOT get here. Hence: PANIC! + _ => panic!("Programming error. We're not supposed to get here.") + }; + Ufo { row, column, direction } + } + + fn mov(&mut self, direction: Direction) { + let ufo_str_len = UFO_STR.len() as i16; + + match direction { + Down => { todo!("Implement crash movement (down + direction)") } + _ => { + match self.direction { + Left => { + if self.column >= -ufo_str_len { + self.column -= 1; + } + } + Right => { + if self.column < WIDTH as i16 { + self.column += 1; + } + } + _ => { /* Ignore, this can't be the case. */ } + }; + } + }; + } + + fn draw(&self) { + let mut body: String; + let ufo_str_len = UFO_STR.len() as i16; + + if self.column < -ufo_str_len { + // left outside + body = String::new(); + } else if self.column < 0 { + // left transitioning in/out + body = String::new(); + } else if self.column < (WIDTH as i16 - ufo_str_len) { + // normal range + body = String::from(UFO_STR); + match self.direction { + Left => { body.push(' ') } + Right => { body = String::from(" ") + body.as_str() } + Down => { todo!("How do we handle the downward movement?") } + _ => {} + } + let max_len = WIDTH as i16 - self.column; + body.truncate(max_len as usize) + } else if self.column < WIDTH as i16 { + // right transitioning in/out + body = String::new(); + } else { + // right outside + body = String::new(); + } + print_str_at(if self.column < 0 { 0 } else { self.column as u16 }, self.row, body.as_str()) + } + + fn is_on_screen(&self) -> bool { + self.column > -(UFO_STR.len() as i16) && self.column < WIDTH as i16 + } +} + +impl Movable for Tank { + fn create() -> Tank { + let column = (WIDTH - TANK_STR.len() as u16) / 2; + Tank { column } + } + + fn mov(&mut self, direction: Direction) { + todo!("Implement tank movement") + } + + fn draw(&self) { + todo!("Implement drawing tank") + } + + fn is_on_screen(&self) -> bool { + todo!("Implement tank visibility check") + } +} + +pub struct Ufo { + row: u16, + column: i16, + direction: Direction, +} + +pub struct Tank { + column: u16, +} diff --git a/src/terminal.rs b/src/terminal.rs new file mode 100644 index 0000000..6984e92 --- /dev/null +++ b/src/terminal.rs @@ -0,0 +1,109 @@ +use std::io::{stdout, Write}; + +use crossterm::{execute, queue}; +use crossterm::cursor::{Hide, MoveTo, RestorePosition, SavePosition, Show}; +use crossterm::style::Print; +use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetSize, size}; + +use crate::{FOOTER_ROW, HEADER_ROW, HEIGHT, WIDTH}; + +pub struct Terminal { + cols: u16, + rows: u16, +} + +pub fn setup() -> Terminal { + let (cols, rows) = size() + .expect("Failed to get terminal screen size"); + + execute!( + stdout(), + EnterAlternateScreen, + SetSize(WIDTH, HEIGHT), + Hide + ).expect("Failed to set up terminal screen"); + enable_raw_mode() + .expect("Failed to enable raw terminal mode"); + + write_header(); + write_footer(); + Terminal { cols, rows } +} + +pub fn restore(terminal: Terminal) { + disable_raw_mode() + .expect("Failed to disable raw terminal mode"); + execute!( + stdout(), + SetSize(terminal.cols, terminal.rows), + Show, + LeaveAlternateScreen + ) + .expect("Failed to tear down terminal screen"); +} + +pub fn write_header() { + queue!( + stdout(), + MoveTo(0,HEADER_ROW), + Print(center_text("UFO goes RUST! ** by fritteli, ufo@fritteli.ch ** Have fun!", None)) + ).expect("Failed to write to terminal"); + stdout().flush().expect("Failed to write to terminal"); +} + +pub fn write_footer() { + queue!( + stdout(), + MoveTo(0,FOOTER_ROW), + Print(center_text("v0.0.0-dev", None)) + ).expect("Failed to write to terminal"); + stdout().flush().expect("Failed to write to terminal"); +} + +fn center_text(text: &str, filler: Option<&str>) -> String { + let length = text.len(); + let surplus = (WIDTH as i16) - (length as i16) - 2; + if surplus < 0 { + return String::from(text); + } + let left: usize = (surplus as usize) / 2; + let right: usize; + if surplus % 2 == 0 { + right = left; + } else { + right = left + 1; + } + let left_fill = filler.unwrap_or("=").repeat(left); + let right_fill = filler.unwrap_or("=").repeat(right); + format!("{} {} {}", left_fill, text, right_fill) +} + +fn print_char_at(col: u16, row: u16, char: char) { + execute!( + stdout(), + SavePosition, + MoveTo(col, row), + Print(char), + RestorePosition + ) + .expect("Failed to print char to terminal"); +} + +pub fn print_str_at(col: u16, row: u16, s: &str) { + execute!( + stdout(), + SavePosition, + MoveTo(col, row), + Print(s), + RestorePosition + ) + .expect("Failed to print string to terminal"); +} + +pub fn clear_pos(col: u16, row: u16) { + print_char_at(col, row, ' '); +} + +pub fn clear_stretch(col: u16, row: u16, len: usize) { + print_str_at(col, row, " ".repeat(len).as_str()); +}