Skip to content

Instantly share code, notes, and snippets.

@justinledwards
Last active November 2, 2023 21:26
Show Gist options
  • Save justinledwards/c1878e9240797763d83f57baf41e46f9 to your computer and use it in GitHub Desktop.
Save justinledwards/c1878e9240797763d83f57baf41e46f9 to your computer and use it in GitHub Desktop.
i love lamp
[package]
name = "rust-lavalamp"
version = "0.1.0"
edition = "2018"
# Dependencies
[dependencies]
rand = "0.8"
ncurses = "5.101.0"
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));
}
}
[package]
name = "rust-lavalamp"
version = "0.1.0"
edition = "2018"
# Dependencies
[dependencies]
rand = "0.8"
crossterm = "0.25"
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