diff --git a/src/bar.rs b/src/bar.rs index e2c8dab..9ebf194 100644 --- a/src/bar.rs +++ b/src/bar.rs @@ -1,60 +1,15 @@ -use crate::Currency; -use servicepoint::{CharGridMutExt, GridMut, WindowMut}; -use std::time::Duration; - +use servicepoint::GridMut; #[derive(Debug, Clone, Copy)] pub struct Bar { - name: &'static str, - progress: f64, - pub(crate) enabled: bool, - pub(crate) speed: f64, - pub(crate) productivity: f64, + pub progress: f64, } impl Bar { - pub(crate) fn new(productivity: f64, speed: f64, name: &'static str) -> Self { - Self { - name, - productivity, - speed, - enabled: false, - progress: 0f64, - } + pub fn new() -> Self { + Self { progress: 0.0 } } - pub(crate) fn name(&self) -> &'static str { - self.name - } - - pub(crate) fn progress(&mut self, delta: Duration) -> Currency { - if !self.enabled { - return 0.0; - } - - let extra_progress = delta.mul_f64(self.speed).as_secs_f64(); - let progress = self.progress + extra_progress; - let completions = progress.floor(); - self.progress = progress - completions; - - completions * self.productivity - } - - pub fn draw, P: GridMut>(&self, chars: &mut C, bitmap: &mut P) { - if !self.enabled { - return; - } - - let mut bitmap = WindowMut::new(bitmap, 0, 0, bitmap.width() / 2, bitmap.height()).unwrap(); - let mut chars = WindowMut::new( - chars, - chars.width() / 2 + 1, - 0, - chars.width() / 2 - 1, - chars.height(), - ) - .unwrap(); - - chars.set_row_str(0, self.name).unwrap(); + pub fn draw>(&self, bitmap: &mut P) { bitmap.fill(false); let margin = 1; diff --git a/src/border_panel.rs b/src/border_panel.rs new file mode 100644 index 0000000..9834306 --- /dev/null +++ b/src/border_panel.rs @@ -0,0 +1,97 @@ +use servicepoint::{CharGridMutExt, Grid, GridMut, TILE_SIZE, WindowMut}; +use std::io::BufRead; + +pub type BorderPattern = [bool; TILE_SIZE]; + +pub const OUTER_BORDER: BorderPattern = [false, false, false, true, true, false, true, false]; +pub const INNER_BORDER: BorderPattern = [false, false, false, true, true, false, false, false]; + +pub fn draw_border_panel<'t, C: GridMut, P: GridMut>( + mut chars: WindowMut<'t, char, C>, + mut bitmap: WindowMut<'t, bool, P>, + label: &'static str, + border_pattern: BorderPattern, +) -> (WindowMut<'t, char, C>, WindowMut<'t, bool, P>) { + let tile_width = chars.width(); + let tile_height = chars.height(); + let pixel_width = bitmap.width(); + let pixel_height = bitmap.height(); + + chars + .window_mut(2, 0, tile_width - 4, 1) + .unwrap() + .set_row_str(0, label) + .unwrap(); + + // border top+bottom + for tile_x in 1..tile_width - 1 { + for inner_x in 0..TILE_SIZE { + let x = tile_x * TILE_SIZE + inner_x; + let draw_top = tile_x <= 1 || tile_x >= label.chars().count() + 2; + + for inner_y in 0..TILE_SIZE { + let val = border_pattern[inner_y]; + if draw_top { + bitmap.set(x, inner_y, val); + } + bitmap.set(x, pixel_height - inner_y - 1, val); + } + } + } + + // border left + right + for tile_y in 1..tile_height - 1 { + for inner_y in 0..TILE_SIZE { + let y = tile_y * TILE_SIZE + inner_y; + for inner_x in 0..TILE_SIZE { + let val = border_pattern[inner_x]; + + bitmap.set(inner_x, y, val); + bitmap.set(pixel_width - inner_x - 1, y, val); + } + } + } + + // edges + for pat_index in 0..TILE_SIZE { + if !border_pattern[pat_index] { + continue; + } + + for extend in 0..=TILE_SIZE - pat_index { + bitmap.set(TILE_SIZE - extend, pat_index, true); + bitmap.set(pixel_width - 1 - TILE_SIZE + extend, pat_index, true); + + bitmap.set(pat_index, TILE_SIZE - extend, true); + bitmap.set(pat_index, pixel_height - 1 - TILE_SIZE + extend, true); + + bitmap.set(TILE_SIZE - extend, pixel_height - pat_index - 1, true); + bitmap.set( + pixel_width - 1 - TILE_SIZE + extend, + pixel_height - pat_index - 1, + true, + ); + + bitmap.set(pixel_width - pat_index - 1, TILE_SIZE - extend, true); + bitmap.set( + pixel_width - pat_index - 1, + pixel_height - 1 - TILE_SIZE + extend, + true, + ); + } + } + + let (chars, _) = chars.split_horizontal_mut(tile_width - 1).unwrap(); + let (_, chars) = chars.split_horizontal_mut(1).unwrap(); + let (chars, _) = chars.split_vertical_mut(tile_height - 1).unwrap(); + let (_, chars) = chars.split_vertical_mut(1).unwrap(); + + let (bitmap, _) = bitmap + .split_horizontal_mut(pixel_width - TILE_SIZE) + .unwrap(); + let (_, bitmap) = bitmap.split_horizontal_mut(TILE_SIZE).unwrap(); + let (bitmap, _) = bitmap.split_vertical_mut(pixel_height - TILE_SIZE).unwrap(); + let (_, bitmap) = bitmap.split_vertical_mut(TILE_SIZE).unwrap(); + + (chars, bitmap) +} diff --git a/src/game.rs b/src/game.rs index 203abe3..69bcf63 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,6 +1,10 @@ -use crate::unlocks::{Unlock, UnlockSystem}; -use crate::{Currency, bar::Bar}; -use servicepoint::{Bitmap, CharGrid, CharGridMutExt, TILE_SIZE, WindowMut}; +use crate::border_panel::{INNER_BORDER, OUTER_BORDER, draw_border_panel}; +use crate::row::Row; +use crate::{ + Currency, + unlocks::{Unlock, UnlockSystem}, +}; +use servicepoint::{Bitmap, CharGrid, CharGridMutExt, GridMut, TILE_SIZE, WindowMut}; use std::time::Duration; #[derive(Debug)] @@ -12,14 +16,14 @@ pub struct Game { #[derive(Debug)] pub struct State { - pub(crate) currency: Currency, - pub(crate) speed: f64, - pub(crate) productivity: f64, - pub(crate) bars: Vec, + pub currency: Currency, + pub speed: f64, + pub productivity: f64, + pub rows: Vec, } impl Game { - const BAR_NAMES: [&'static str; 11] = [ + const BAR_NAMES: [&'static str; 10] = [ "Powering infrastructure", "Dusting ServicePoint", "Activating colorful lights", @@ -30,7 +34,7 @@ impl Game { "Untangling 'block chain'", "Refilling sticker box", "Setting room to public", - "Welcoming creatures", + // "Welcoming creatures", ]; pub fn new() -> Self { @@ -38,7 +42,7 @@ impl Game { .iter() .enumerate() .map(|(index, name)| { - Bar::new( + Row::new( 2usize.pow(index as u32) as f64, 1.0 * (0.5f64.powi(index as i32)), *name, @@ -46,7 +50,7 @@ impl Game { }) .collect(); let state = State { - bars, + rows: bars, currency: 1f64, speed: 1f64, productivity: 1f64, @@ -58,93 +62,80 @@ impl Game { } } - pub(crate) fn progress(&mut self, delta: Duration) { + pub fn progress(&mut self, delta: Duration) { let adjusted_delta = delta.mul_f64(self.state.speed); let extra_currency = self.state.productivity * self .state - .bars + .rows .iter_mut() .map(|bar| bar.progress(adjusted_delta)) .sum::(); self.state.currency += extra_currency; self.total_currency += extra_currency; - self.unlocks.check_next(&mut self.state); + self.unlocks.progress(&mut self.state); } - pub(crate) fn draw( + pub fn draw( &self, - text_layer: &mut WindowMut, - pixel_layer: &mut WindowMut, + text_layer: WindowMut, + pixel_layer: WindowMut, ) { - text_layer - .set_row_str(0, "Discordia Boot Procedure") + let (text_layer, pixel_layer) = draw_border_panel( + text_layer, + pixel_layer, + " Discordia Boot Procedure ", + OUTER_BORDER, + ); + + let unlocks_height = 3 + 2; + let (unlocks_text, text_layer) = text_layer.split_vertical_mut(unlocks_height).unwrap(); + let (unlocks_pixel, pixel_layer) = pixel_layer + .split_vertical_mut(unlocks_height * TILE_SIZE) .unwrap(); - self.draw_bars(text_layer, pixel_layer, 1); + self.unlocks.draw(unlocks_text, unlocks_pixel); - let mut row = text_layer.height() - 1; - self.draw_stats(text_layer, row); - row -= 1; + let bars_height = self.state.rows.len() + 2; + let (bars_text, text_layer) = text_layer.split_vertical_mut(bars_height).unwrap(); + let (bars_pixel, _) = pixel_layer + .split_vertical_mut(bars_height * TILE_SIZE) + .unwrap(); + self.draw_bars(bars_text, bars_pixel); - if let Some(next_upgrade) = self.unlocks.peek_next() { - self.draw_next_upgrade(text_layer, row, next_upgrade); - row -= 1; - } - - if let Some(prev_unlock) = self.unlocks.previous() { - self.draw_prev_unlock(text_layer, row, prev_unlock); - } + let free_row_bottom = text_layer.height() - 1; + self.draw_stats(text_layer, free_row_bottom); } - fn draw_next_upgrade(&self, text_layer: &mut WindowMut, row: usize, next_upgrade: &Unlock) { - text_layer - .window_mut(0, row, text_layer.width(), 1) - .unwrap() - .set_row_str(0, &format!("Next unlock: {:.0}/{} Hacks", self.state.currency, next_upgrade.cost)) - .unwrap(); - } - - fn draw_stats(&self, text_layer: &mut WindowMut, row: usize) { - text_layer - .window_mut(0, row, text_layer.width() / 2, 1) - .unwrap() - .set_row_str(0, &format!("Hack Score: {:.2}", self.total_currency)) + fn draw_stats>(&self, text_layer: WindowMut, row: usize) { + let middle = text_layer.width() / 2; + let (mut left, mut right) = text_layer.split_horizontal_mut(middle).unwrap(); + left.set_row_str(0, &format!("Hack Score: {:.2}", self.total_currency)) .unwrap(); if self.unlocks.bought() > 0 { - text_layer - .window_mut( - text_layer.width() / 2, - row, - text_layer.width() / 2, - 1, - ) - .unwrap() + right .set_row_str(0, &format!(" Unlocks: {}", self.unlocks.bought())) .unwrap(); } } - fn draw_bars(&self, text_layer: &mut WindowMut, pixel_layer: &mut WindowMut, mut row: usize) { - for bar in self.state.bars.iter() { + fn draw_bars, P: GridMut>( + &self, + text_layer: WindowMut, + pixel_layer: WindowMut, + ) { + let (mut text_layer, mut pixel_layer) = + draw_border_panel(text_layer, pixel_layer, " Processes ", INNER_BORDER); + + for (index, row) in self.state.rows.iter().enumerate() { let mut bar_window = pixel_layer - .window_mut(0, row * TILE_SIZE, pixel_layer.width(), TILE_SIZE) + .window_mut(0, index * TILE_SIZE, pixel_layer.width(), TILE_SIZE) .unwrap(); let mut label_window = text_layer - .window_mut(0, row, text_layer.width(), 1) + .window_mut(0, index, text_layer.width(), 1) .unwrap(); - bar.draw(&mut label_window, &mut bar_window); - - row += 1; + row.draw(&mut label_window, &mut bar_window); } } - - fn draw_prev_unlock(&self, chars: &mut WindowMut, row: usize, prev: &String) { - chars - .window_mut(0, row, chars.width(), 1) - .unwrap() - .set_row_str(0, &format!("Unlocked: {}", prev)) - .unwrap(); - } } diff --git a/src/main.rs b/src/main.rs index b1129d4..f7edece 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,9 @@ use servicepoint::{ use std::{net::UdpSocket, thread::sleep, time::Instant}; mod bar; +mod border_panel; mod game; +mod row; mod unlocks; type Currency = f64; @@ -35,13 +37,13 @@ fn main() { chars.fill(' '); pixels.fill(false); - let mut chars_view = chars + let chars_view = chars .window_mut(0, 0, chars.width(), chars.height()) .unwrap(); - let mut pixels_view = pixels + let pixels_view = pixels .window_mut(0, 0, pixels.width(), pixels.height()) .unwrap(); - state.draw(&mut chars_view, &mut pixels_view); + state.draw(chars_view, pixels_view); connection .send_command(CharGridCommand::from(chars.clone())) diff --git a/src/row.rs b/src/row.rs new file mode 100644 index 0000000..dc01eee --- /dev/null +++ b/src/row.rs @@ -0,0 +1,66 @@ +use crate::{Currency, bar::Bar}; +use servicepoint::{CharGridMutExt, GridMut, WindowMut}; +use std::time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct Row { + name: &'static str, + pub enabled: bool, + pub speed: f64, + pub productivity: f64, + bar: Bar, +} + +impl Row { + pub fn new(productivity: f64, speed: f64, name: &'static str) -> Self { + Self { + name, + productivity, + speed, + enabled: false, + bar: Bar { progress: 0f64 }, + } + } + + pub fn name(&self) -> &'static str { + self.name + } + + pub fn progress(&mut self, delta: Duration) -> Currency { + if !self.enabled { + return 0.0; + } + + let extra_progress = delta.mul_f64(self.speed).as_secs_f64(); + let progress = self.bar.progress + extra_progress; + let completions = progress.floor(); + self.bar.progress = progress - completions; + + completions * self.productivity + } + + pub fn draw, P: GridMut>(&self, chars: &mut C, bitmap: &mut P) { + if !self.enabled { + return; + } + + let mut bitmap = WindowMut::new(bitmap, 0, 0, bitmap.width() / 2, bitmap.height()).unwrap(); + self.bar.draw(&mut bitmap); + + let mut chars = WindowMut::new( + chars, + chars.width() / 2 + 1, + 0, + chars.width() / 2 - 1, + chars.height(), + ) + .unwrap(); + + chars + .set_row_str( + 0, + &*self.name.chars().take(chars.width()).collect::(), + ) + .unwrap(); + } +} diff --git a/src/unlocks.rs b/src/unlocks.rs index c4205a6..6f3184d 100644 --- a/src/unlocks.rs +++ b/src/unlocks.rs @@ -1,20 +1,26 @@ -use crate::Currency; -use crate::game::{Game, State}; -use std::collections::VecDeque; -use std::fmt::{Debug, Formatter, format}; -use std::ops::Mul; +use crate::border_panel::{INNER_BORDER, draw_border_panel}; +use crate::{Currency, bar::Bar, game::State, row::Row}; +use servicepoint::{CharGridMutExt, GridMut, TILE_SIZE, WindowMut}; +use std::{ + collections::VecDeque, + fmt::{Debug, Formatter}, + iter::Map, + ops::{Mul, Range}, +}; #[derive(Debug)] -pub(crate) struct UnlockSystem { +pub struct UnlockSystem { bought: usize, last_unlock: Option, unlock_queue: VecDeque, + all_unlocks_bar: Bar, + next_unlock_bar: Bar, } -pub(crate) struct Unlock { - pub(crate) name: String, - pub(crate) cost: Currency, - pub(crate) apply: Box, +pub struct Unlock { + pub name: String, + pub cost: Currency, + pub apply: Box, } impl Debug for Unlock { @@ -27,93 +33,147 @@ impl Debug for Unlock { } impl UnlockSystem { - pub(crate) fn new(start_state: &State) -> Self { - let mut upgrades = start_state - .bars - .iter() - .enumerate() - .flat_map(|(index, bar)| { - let initial_cost = 10usize.pow(index as u32); - [ - Unlock { - name: format!("Start {}", bar.name()), - cost: initial_cost as f64, - apply: Box::new(move |game| { - game.bars[index].enabled = true; - }), - }, - Unlock { - name: format!("{} productivity", bar.name()), - cost: initial_cost.mul(2) as f64, - apply: Box::new(move |game| { - game.bars[index].productivity *= 2.0; - }), - }, - Unlock { - name: format!("{} speed", bar.name()), - cost: initial_cost.mul(20) as f64, - apply: Box::new(move |game| { - game.bars[index].speed *= 1.5; - }), - }, - ] - }) - .chain( - (1..10).map(|level| - Unlock { - name: format!("The answer {}", level), - cost: (42usize * 10usize.pow(level)) as f64, - apply: Box::new(move |game| { - game.speed *= 1.1; - }), - } - ) - ) - .chain( - (1..10).map(|level| - Unlock { - name: format!("??? {}", level), - cost: (23usize * 10usize.pow(level)) as f64, - apply: Box::new(move |game| { - game.productivity *= 1.1; - }), - }, - ) - ) + pub fn new(start_state: &State) -> Self { + let mut upgrades = Self::gen_bar_upgrades(&start_state.rows) + .chain(Self::gen_speed_upgrades()) + .chain(Self::gen_productivity_upgrades()) .collect::>(); upgrades.sort_by(|l, r| l.cost.total_cmp(&r.cost)); - let unlock_queue = upgrades.into(); - Self { - unlock_queue, + unlock_queue: upgrades.into(), bought: 0, - last_unlock: None + last_unlock: None, + all_unlocks_bar: Bar::new(), + next_unlock_bar: Bar::new(), } } - pub(crate) fn peek_next(&self) -> Option<&Unlock> { + pub fn peek_next(&self) -> Option<&Unlock> { self.unlock_queue.front() } - pub(crate) fn check_next(&mut self, state: &mut State) { + pub fn progress(&mut self, state: &mut State) { if let Some(next_upgrade) = self.peek_next() { if next_upgrade.cost <= state.currency { - log::info!("Applying upgrade {:?}", next_upgrade); - let next_upgrade = self.unlock_queue.pop_front().unwrap(); - state.currency -= next_upgrade.cost; - (next_upgrade.apply)(state); - self.last_unlock = Some(next_upgrade.name); - self.bought += 1; + self.buy_next(state); + self.next_unlock_bar.progress = 0.0; + } else { + self.next_unlock_bar.progress = state.currency / next_upgrade.cost; } } } - pub(crate) fn bought(&self) -> usize { + pub fn bought(&self) -> usize { self.bought } - pub(crate) fn previous(&self) -> Option<&String> { + pub fn previous(&self) -> Option<&String> { self.last_unlock.as_ref() } + + pub fn draw, P: GridMut>( + &self, + chars: WindowMut, + bitmap: WindowMut, + ) { + let (mut chars, mut bitmap) = draw_border_panel(chars, bitmap, " Unlocks ", INNER_BORDER); + let mut free_row_bottom = chars.height() - 1; + + let pixel_width = bitmap.width(); + self.all_unlocks_bar.draw( + &mut WindowMut::new( + &mut bitmap, + 0, + free_row_bottom * TILE_SIZE, + pixel_width, + TILE_SIZE, + ) + .unwrap(), + ); + free_row_bottom -= 1; + + self.next_unlock_bar.draw( + &mut WindowMut::new( + &mut bitmap, + 0, + free_row_bottom * TILE_SIZE, + pixel_width, + TILE_SIZE, + ) + .unwrap(), + ); + free_row_bottom -= 1; + + if let Some(prev_unlock) = self.previous() { + let char_width = chars.width(); + WindowMut::new(&mut chars, 0, free_row_bottom, char_width, 1) + .unwrap() + .set_row_str(0, &format!("Unlocked: {}", prev_unlock)) + .unwrap(); + } + } + + fn buy_next(&mut self, state: &mut State) { + let next_upgrade = self.unlock_queue.pop_front().unwrap(); + log::info!("Applying upgrade {:?}", &next_upgrade); + + state.currency -= next_upgrade.cost; + (next_upgrade.apply)(state); + + self.last_unlock = Some(next_upgrade.name); + self.bought += 1; + + let total = self.bought + self.unlock_queue.len(); + self.all_unlocks_bar.progress = self.bought as f64 / total as f64; + } + + fn gen_bar_upgrades(bars: &Vec) -> impl Iterator { + bars.iter().enumerate().flat_map(|(index, bar)| { + let initial_cost = 10usize.pow(index as u32); + [ + Unlock { + name: format!("Start {}", bar.name()), + cost: initial_cost as f64, + apply: Box::new(move |game| { + game.rows[index].enabled = true; + }), + }, + Unlock { + name: format!("{} productivity", bar.name()), + cost: initial_cost.mul(2) as f64, + apply: Box::new(move |game| { + game.rows[index].productivity *= 2.0; + }), + }, + Unlock { + name: format!("{} speed", bar.name()), + cost: initial_cost.mul(20) as f64, + apply: Box::new(move |game| { + game.rows[index].speed *= 1.5; + }), + }, + ] + }) + } + + fn gen_speed_upgrades() -> impl Iterator { + (1..10).map(|level| Unlock { + name: format!("The answer {}", level), + cost: (42usize * 10usize.pow(level)) as f64, + apply: Box::new(move |game| { + game.speed *= 1.1; + }), + }) + } + + fn gen_productivity_upgrades() -> Map, fn(u32) -> Unlock> { + (1..10).map(|level| Unlock { + name: format!("??? {}", level), + cost: (23usize * 10usize.pow(level)) as f64, + apply: Box::new(move |game| { + game.productivity *= 1.1; + }), + }) + } }