Initial commit
- Let max. 100 UFOs cross the screen.
This commit is contained in:
commit
ea6093bd0a
15 changed files with 765 additions and 0 deletions
77
src/main.rs
Normal file
77
src/main.rs
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
116
src/movables.rs
Normal file
116
src/movables.rs
Normal file
|
|
@ -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<T> {
|
||||
fn create() -> T;
|
||||
fn mov(&mut self, direction: Direction);
|
||||
fn draw(&self);
|
||||
fn is_on_screen(&self) -> bool;
|
||||
}
|
||||
|
||||
impl Movable<Ufo> 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<Tank> 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,
|
||||
}
|
||||
109
src/terminal.rs
Normal file
109
src/terminal.rs
Normal file
|
|
@ -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());
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue