Last active
November 2, 2023 21:26
-
-
Save justinledwards/c1878e9240797763d83f57baf41e46f9 to your computer and use it in GitHub Desktop.
i love lamp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "rust-lavalamp" | |
version = "0.1.0" | |
edition = "2018" | |
# Dependencies | |
[dependencies] | |
rand = "0.8" | |
ncurses = "5.101.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate ncurses; | |
extern crate rand; | |
use ncurses::*; | |
use rand::random; | |
use std::thread::sleep; | |
use std::time::Duration; | |
const WIDTH: usize = 300; | |
const HEIGHT: usize = 100; | |
const START_VOLUME_PERCENTAGE: f64 = 0.31; | |
const MAX_VARIATION: f64 = 0.8; | |
struct Bubble { | |
x: f64, | |
y: f64, | |
radius: f64, | |
color: i16, | |
dy: f64, | |
dx: f64, // Added dx for side movement | |
angle: f64, // Angle for sinusoidal side drift | |
} | |
impl Bubble { | |
fn new(x: f64, y: f64, radius: f64, color: i16, dy: f64) -> Self { | |
Bubble { | |
x, | |
y, | |
radius, | |
color, | |
dy, | |
dx: 0.0, | |
angle: random::<f64>() * std::f64::consts::PI * 2.0, | |
} | |
} | |
fn volume(&self) -> f64 { | |
(4.0 / 3.0) * std::f64::consts::PI * self.radius.powi(3) | |
} | |
fn can_merge_with(&self, other: &Bubble) -> bool { | |
let distance = ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt(); | |
distance < (self.radius + other.radius) * 2.0 | |
} | |
fn merge_with(&mut self, other: Bubble) { | |
let new_volume = self.volume() + other.volume(); | |
self.radius = (new_volume * (3.0 / (4.0 * std::f64::consts::PI))).cbrt(); | |
self.x = (self.x + other.x) / 2.0; | |
self.y = (self.y + other.y) / 2.0; | |
} | |
fn intensity_at(&self, x: f64, y: f64) -> f64 { | |
let dx = x - self.x; | |
let dy = y - self.y * 0.5; // Adjusting in the Y direction to make bubbles "fatter" | |
if dx * dx + 1.5 * dy * dy <= self.radius * self.radius { // Adjusting shape | |
let distance = (dx * dx + dy * dy).sqrt(); | |
1.0 - distance / self.radius | |
} else { | |
0.0 | |
} | |
} | |
} | |
fn main() { | |
initscr(); | |
curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE); | |
noecho(); | |
nodelay(stdscr(), true); | |
keypad(stdscr(), true); | |
start_color(); | |
init_pair(1, COLOR_RED, COLOR_BLACK); | |
init_pair(2, COLOR_YELLOW, COLOR_BLACK); | |
init_pair(3, COLOR_GREEN, COLOR_BLACK); | |
init_pair(4, COLOR_BLUE, COLOR_BLACK); | |
let screen_volume = (WIDTH as f64) * (HEIGHT as f64); | |
let target_bubble_volume = screen_volume * START_VOLUME_PERCENTAGE; | |
let variation = target_bubble_volume * MAX_VARIATION; | |
let mut bubbles: Vec<Bubble> = Vec::new(); | |
while | |
bubbles | |
.iter() | |
.map(|b| b.volume()) | |
.sum::<f64>() < target_bubble_volume - variation | |
{ | |
let radius = (random::<f64>() * 3.0 + 1.0).min( | |
(target_bubble_volume / std::f64::consts::PI).cbrt() / 2.0 | |
); | |
let x = random::<f64>() * ((WIDTH as f64) - 2.0 * radius) + radius; | |
let y = random::<f64>() * ((HEIGHT as f64) - 2.0 * radius) + radius; | |
let dy = if random::<f32>() < 0.5 { 0.5 } else { -0.5 }; | |
let color = ((random::<usize>() % 4) + 1) as i16; | |
bubbles.push(Bubble::new(x, y, radius, color, dy)); | |
} | |
loop { | |
for bubble in &mut bubbles { | |
bubble.y += bubble.dy; | |
bubble.angle += 0.05; | |
bubble.dx = 0.5 * bubble.radius * bubble.angle.sin(); | |
bubble.x += bubble.dx; | |
// Keep the bubble within the screen boundaries | |
if bubble.y < 0.0 || bubble.y > (HEIGHT as f64) { | |
bubble.dy = -bubble.dy; | |
} | |
if bubble.x < 0.0 || bubble.x > (WIDTH as f64) { | |
bubble.dx = -bubble.dx; | |
} | |
} | |
let mut merge_occurred = true; | |
while merge_occurred { | |
merge_occurred = false; | |
let mut merges = Vec::new(); | |
for i in 0..bubbles.len() { | |
for j in i + 1..bubbles.len() { | |
if bubbles[i].can_merge_with(&bubbles[j]) { | |
merges.push((i, j)); | |
break; | |
} | |
} | |
} | |
for (i, j) in merges.iter().rev() { | |
let other = bubbles.remove(*j); | |
bubbles[*i].merge_with(other); | |
merge_occurred = true; | |
} | |
} | |
clear(); | |
for y in 0..HEIGHT { | |
for x in 0..WIDTH { | |
let mut intensity = 0.0; | |
let mut color = 0; | |
for bubble in &bubbles { | |
if bubble.intensity_at(x as f64, y as f64) > intensity { | |
intensity = bubble.intensity_at(x as f64, y as f64); | |
color = bubble.color; | |
} | |
} | |
attron(COLOR_PAIR(color)); | |
mvaddch(y as i32, x as i32, match intensity { | |
_ if intensity <= 0.2 => ' ' as u32, | |
_ if intensity <= 0.5 => '.' as u32, | |
_ if intensity <= 0.8 => 'o' as u32, | |
_ => 'O' as u32, | |
}); | |
attroff(COLOR_PAIR(color)); | |
} | |
} | |
refresh(); | |
sleep(Duration::from_millis(100)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "rust-lavalamp" | |
version = "0.1.0" | |
edition = "2018" | |
# Dependencies | |
[dependencies] | |
rand = "0.8" | |
crossterm = "0.25" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate crossterm; | |
extern crate rand; | |
use std::io::Write; | |
use crossterm::cursor::{ Hide, MoveTo }; | |
use crossterm::event::{ poll, read, Event, KeyCode }; | |
use crossterm::execute; | |
use crossterm::terminal::{ Clear, ClearType, DisableLineWrap, EnableLineWrap }; | |
use crossterm::style::{ Color, SetForegroundColor }; | |
use rand::random; | |
use std::thread::sleep; | |
use std::time::Duration; | |
const WIDTH: usize = 300; | |
const HEIGHT: usize = 100; | |
const START_VOLUME_PERCENTAGE: f64 = 0.31; | |
const MAX_VARIATION: f64 = 0.8; | |
struct Bubble { | |
x: f64, | |
y: f64, | |
radius: f64, | |
color: Color, | |
dy: f64, | |
dx: f64, // Added dx for side movement | |
angle: f64, // Angle for sinusoidal side drift | |
} | |
impl Bubble { | |
fn new(x: f64, y: f64, radius: f64, color: Color, dy: f64) -> Self { | |
Bubble { | |
x, | |
y, | |
radius, | |
color, | |
dy, | |
dx: 0.0, | |
angle: random::<f64>() * std::f64::consts::PI * 2.0, | |
} | |
} | |
fn volume(&self) -> f64 { | |
(4.0 / 3.0) * std::f64::consts::PI * self.radius.powi(3) | |
} | |
fn can_merge_with(&self, other: &Bubble) -> bool { | |
let distance = ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt(); | |
distance < (self.radius + other.radius) * 2.0 | |
} | |
fn merge_with(&mut self, other: Bubble) { | |
let new_volume = self.volume() + other.volume(); | |
self.radius = (new_volume * (3.0 / (4.0 * std::f64::consts::PI))).cbrt(); | |
self.x = (self.x + other.x) / 2.0; | |
self.y = (self.y + other.y) / 2.0; | |
} | |
fn intensity_at(&self, x: f64, y: f64) -> f64 { | |
let dx = x - self.x; | |
let dy = y - self.y * 0.5; // Adjusting in the Y direction to make bubbles "fatter" | |
if dx * dx + 1.5 * dy * dy <= self.radius * self.radius { | |
// Adjusting shape | |
let distance = (dx * dx + dy * dy).sqrt(); | |
1.0 - distance / self.radius | |
} else { | |
0.0 | |
} | |
} | |
} | |
fn main() { | |
let mut stdout = std::io::stdout(); | |
execute!(stdout, Clear(ClearType::All), DisableLineWrap, Hide).unwrap(); | |
let screen_volume = (WIDTH as f64) * (HEIGHT as f64); | |
let target_bubble_volume = screen_volume * START_VOLUME_PERCENTAGE; | |
let variation = target_bubble_volume * MAX_VARIATION; | |
let mut bubbles: Vec<Bubble> = Vec::new(); | |
let colors = [Color::Red, Color::Yellow, Color::Green, Color::Blue]; | |
while | |
bubbles | |
.iter() | |
.map(|b| b.volume()) | |
.sum::<f64>() < target_bubble_volume - variation | |
{ | |
let radius = (random::<f64>() * 3.0 + 1.0).min( | |
(target_bubble_volume / std::f64::consts::PI).cbrt() / 2.0 | |
); | |
let x = random::<f64>() * ((WIDTH as f64) - 2.0 * radius) + radius; | |
let y = random::<f64>() * ((HEIGHT as f64) - 2.0 * radius) + radius; | |
let dy = if random::<f32>() < 0.5 { 0.5 } else { -0.5 }; | |
let color = colors[random::<usize>() % colors.len()]; | |
bubbles.push(Bubble::new(x, y, radius, color, dy)); | |
} | |
loop { | |
for bubble in &mut bubbles { | |
bubble.y += bubble.dy; | |
bubble.angle += 0.05; | |
bubble.dx = 0.5 * bubble.radius * bubble.angle.sin(); | |
bubble.x += bubble.dx; | |
// Keep the bubble within the screen boundaries | |
if bubble.y < 0.0 || bubble.y > (HEIGHT as f64) { | |
bubble.dy = -bubble.dy; | |
} | |
if bubble.x < 0.0 || bubble.x > (WIDTH as f64) { | |
bubble.dx = -bubble.dx; | |
} | |
} | |
let mut merge_occurred = true; | |
while merge_occurred { | |
merge_occurred = false; | |
let mut merges = Vec::new(); | |
for i in 0..bubbles.len() { | |
for j in i + 1..bubbles.len() { | |
if bubbles[i].can_merge_with(&bubbles[j]) { | |
merges.push((i, j)); | |
break; | |
} | |
} | |
} | |
for (i, j) in merges.iter().rev() { | |
let other = bubbles.remove(*j); | |
bubbles[*i].merge_with(other); | |
merge_occurred = true; | |
} | |
} | |
execute!(stdout, Clear(ClearType::All)).unwrap(); | |
for y in 0..HEIGHT { | |
for x in 0..WIDTH { | |
let mut intensity = 0.0; | |
let mut color = Color::Reset; | |
for bubble in &bubbles { | |
if bubble.intensity_at(x as f64, y as f64) > intensity { | |
intensity = bubble.intensity_at(x as f64, y as f64); | |
color = bubble.color; | |
} | |
} | |
execute!(stdout, SetForegroundColor(color), MoveTo(x as u16, y as u16)).unwrap(); | |
print!("{}", match intensity { | |
_ if intensity <= 0.2 => ' ', | |
_ if intensity <= 0.5 => '.', | |
_ if intensity <= 0.8 => 'o', | |
_ => 'O', | |
}); | |
} | |
} | |
stdout.flush().unwrap(); | |
sleep(Duration::from_millis(100)); | |
if poll(Duration::from_millis(100)).unwrap() { | |
if let Event::Key(key_event) = read().unwrap() { | |
if key_event.code == KeyCode::Esc { | |
break; | |
} | |
} | |
} | |
} | |
execute!(stdout, Clear(ClearType::All), EnableLineWrap).unwrap(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment