diff --git a/Cargo.lock b/Cargo.lock index 95c29e1..99eb40e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1599,9 +1599,9 @@ checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] diff --git a/assets/images/castle.png b/assets/images/castle.png new file mode 100644 index 0000000..8bf7ef0 Binary files /dev/null and b/assets/images/castle.png differ diff --git a/src/main.rs b/src/main.rs index b7455e3..4b80158 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,8 @@ #![allow( clippy::cast_possible_truncation, clippy::cast_sign_loss, - clippy::too_many_lines + clippy::too_many_lines, + clippy::module_name_repetitions )] mod card; @@ -24,15 +25,48 @@ use player::{Player, Stats}; fn main() { nannou::app(Model::init) .update(update) - .loop_mode(LoopMode::RefreshSync) + .loop_mode(LoopMode::Wait) .run(); } +struct Textures { + ant: wgpu::Texture, + castle: wgpu::Texture, +} + +impl Textures { + fn load(app: &App) -> Self { + let ant = wgpu::Texture::from_image( + app, + &image::load_from_memory_with_format( + include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/assets/images/ant.png" + )), + ImageFormat::Png, + ) + .expect("Failed to load image"), + ); + let castle = wgpu::Texture::from_image( + app, + &image::load_from_memory_with_format( + include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/assets/images/castle.png" + )), + ImageFormat::Png, + ) + .expect("Failed to load image"), + ); + Self { ant, castle } + } +} + struct Model { // Store the window ID so we can refer to this specific window later if needed. window: WindowId, font: Font, - texture: wgpu::Texture, + textures: Textures, ups: f64, just_clicked: RwLock, red_hand: RwLock<[Card; HAND_CARD_COUNT]>, @@ -53,20 +87,9 @@ impl Model { .build() .unwrap(); - let texture = wgpu::Texture::from_image( - app, - &image::load_from_memory_with_format( - include_bytes!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/assets/images/ant.png" - )), - ImageFormat::Png, - ) - .expect("Failed to load image"), - ); Self { window, - texture, + textures: Textures::load(app), font: Font::from_bytes(include_bytes!(concat!( env!("CARGO_MANIFEST_DIR"), "/assets/fonts/Monocraft/Monocraft-NF.ttf" @@ -136,56 +159,88 @@ const HAND_CARD_COUNT: usize = 8; const HAND_ASPECT_WIDTH: f32 = 1f32; const HAND_ASPECT_HEIGHT: f32 = 2f32; // Draw the state of your `Model` into the given `Frame` here. - -fn player_panel( - draw: &Draw, - bounds: Rect, - player_stats: &RwLock, - player: Player, - font: Font, -) { - let rect = Rect::from_x_y_w_h(bounds.x(), bounds.y(), 1f32, 5f32).fit_into(&bounds); - let rect = match player { - Player::Red => rect.align_right_of(bounds), - Player::Black => rect.align_left_of(bounds), +fn player_panel(model: &Model, draw: &Draw, bounds: Rect, player: Player) { + let part_bounds = bounds.part_horizontal(match player { + Player::Red => 0.8, + Player::Black => 0.2, + }); + let [castle_bounds, status_bounds] = match player { + Player::Red => part_bounds, + Player::Black => [part_bounds[1], part_bounds[0]], }; - let player_stats = player_stats.read().expect("player stats poisoned"); + let castle_bounds = castle_bounds.top_part(0.7); + let castle_rect = Rect::from_x_y_w_h( + castle_bounds.x(), + castle_bounds.y(), + model.textures.castle.width() as f32, + model.textures.castle.height() as f32, + ) + .fit_into(castle_bounds); + draw.texture(&model.textures.castle) + .xy(castle_rect.xy()) + .wh(castle_rect.wh()) + .finish(); + let status_rect = status_bounds + .align_left_of(status_bounds) + .align_middle_y_of(status_bounds); + + let player_stats = *model + .stats_of(player) + .read() + .expect("player stats poisoned"); let status = match player { - Player::Red => format!( - "{:<3} Builders\n{:<5} Bricks\n{:<3} Soldiers\n{:<4} Weapons\n{:<7} Magi\n{:<3} Crystals\n{:<5} Castle\n{:<6} Fence", - player_stats.builders, - player_stats.bricks, - player_stats.soldiers, - player_stats.weapons, - player_stats.magi, - player_stats.crystals, - player_stats.castle, - player_stats.fence, - ), - Player::Black => format!( - "Builders {:>3}\nBricks {:>5}\nSoldiers {:>3}\nWeapons {:>4}\nMagi {:>7}\nCrystals {:>3}\nCastle {:>5}\nFence {:>6}", - player_stats.builders, - player_stats.bricks, - player_stats.soldiers, - player_stats.weapons, - player_stats.magi, - player_stats.crystals, - player_stats.castle, - player_stats.fence, - ) - }; - drop(player_stats); + Player::Red => format!( + "{:<3} Builders\n{:<5} Bricks\n{:<3} Soldiers\n{:<4} Weapons\n{:<7} Magi\n{:<3} Crystals\n{:<5} Castle\n{:<6} Fence", + player_stats.builders, + player_stats.bricks, + player_stats.soldiers, + player_stats.weapons, + player_stats.magi, + player_stats.crystals, + player_stats.castle, + player_stats.fence, + ), + Player::Black => format!( + "Builders {:>3}\nBricks {:>5}\nSoldiers {:>3}\nWeapons {:>4}\nMagi {:>7}\nCrystals {:>3}\nCastle {:>5}\nFence {:>6}", + player_stats.builders, + player_stats.bricks, + player_stats.soldiers, + player_stats.weapons, + player_stats.magi, + player_stats.crystals, + player_stats.castle, + player_stats.fence, + ) + }; + let character_size = model + .font + .glyph('A') + .standalone() + .get_data() + .expect("No glyph found") + .extents + .unwrap(); + let character_width = character_size.width(); + let character_height = character_size.height(); + + let character_aspect_ratio = character_width as f32 / character_height as f32; + + let font_size = f32::min( + status_bounds.w() / 12f32 / character_aspect_ratio, + status_bounds.h() / 8f32, + ) as u32; + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] let drawing = draw .text(&status) - .xy(rect.xy()) - .wh(rect.wh()) + .xy(status_rect.xy()) + .wh(status_rect.wh()) .color(WHITE) - .font_size((rect.h() * 0.02f32).trunc() as u32) + .font_size(font_size) .align_text_middle_y() - .font(font) + .font(model.font.clone()) .color(match player { Player::Red => RED, Player::Black => WHITE, @@ -213,14 +268,14 @@ fn hand( HAND_CARD_COUNT as f32 * HAND_ASPECT_WIDTH, HAND_ASPECT_HEIGHT, ) - .fit_into(&bounds) + .fit_into(bounds) .align_bottom_of(bounds); let current_player = *model .current_player .read() .expect("current player poisoned"); - for (idx, rect) in hand_rect + for (idx, &rect) in hand_rect .split_horizontal::() .iter() .enumerate() @@ -239,11 +294,11 @@ fn hand( let image_rect = Rect::from_x_y_w_h( rect.x(), rect.y(), - model.texture.width() as f32, - model.texture.height() as f32, + model.textures.ant.width() as f32, + model.textures.ant.height() as f32, ) .fit_into(rect) - .align_bottom_of(*rect); + .align_bottom_of(rect); draw.text(&format!( "{:?}\n{}{}", @@ -275,7 +330,7 @@ fn hand( .font(model.font.clone()) .finish(); - draw.texture(&model.texture) + draw.texture(&model.textures.ant) .xy(image_rect.xy()) .wh(image_rect.wh()) .finish(); @@ -339,22 +394,11 @@ fn view(app: &App, model: &Model, frame: Frame) { let window_rect = window.rect(); - let player_one_bounds = window_rect.left_part(0.2); - player_panel( - &draw, - player_one_bounds, - model.stats_of(Player::Black), - Player::Black, - model.font.clone(), - ); - let player_two_bounds = window_rect.right_part(0.2); - player_panel( - &draw, - player_two_bounds, - model.stats_of(Player::Red), - Player::Red, - model.font.clone(), - ); + let [player_one_rect, player_two_rect] = window_rect.split_horizontal::<2>(); + let player_one_bounds = player_one_rect; + player_panel(model, &draw, player_one_bounds, Player::Black); + let player_two_bounds = player_two_rect; + player_panel(model, &draw, player_two_bounds, Player::Red); let hand_bounds = window_rect.bottom_part(0.3); hand( diff --git a/src/rect_helper.rs b/src/rect_helper.rs index f4aa051..2ffd505 100644 --- a/src/rect_helper.rs +++ b/src/rect_helper.rs @@ -1,28 +1,31 @@ -use nannou::geom::Rect; +use nannou::{geom::Rect, math::num_traits::Float}; -pub trait RectHelper { - fn fit_into(&self, bounds: &Rect) -> Rect; +pub trait RectHelper { + fn fit_into(self, bounds: Rect) -> Rect; - fn bottom_half(&self) -> Rect; - fn bottom_part(&self, part: f32) -> Rect; + fn bottom_half(self) -> Rect; + fn bottom_part(self, part: S) -> Rect; - fn top_half(&self) -> Rect; - fn top_part(&self, part: f32) -> Rect; + fn top_half(self) -> Rect; + fn top_part(self, part: S) -> Rect; - fn left_half(&self) -> Rect; - fn left_part(&self, part: f32) -> Rect; + fn left_half(self) -> Rect; + fn left_part(self, part: S) -> Rect; - fn right_half(&self) -> Rect; - fn right_part(&self, part: f32) -> Rect; + fn right_half(self) -> Rect; + fn right_part(self, part: S) -> Rect; - fn split_horizontal(&self) -> [Rect; N]; - fn split_vertical(&self) -> [Rect; N]; - fn split_horizontal_vec(&self, num: usize) -> Vec>; - fn split_vertical_vec(&self, num: usize) -> Vec>; + fn part_horizontal(self, part: S) -> [Rect; 2]; + fn part_vertical(self, part: S) -> [Rect; 2]; + + fn split_horizontal(self) -> [Rect; N]; + fn split_vertical(self) -> [Rect; N]; + fn split_horizontal_vec(self, num: usize) -> Vec>; + fn split_vertical_vec(self, num: usize) -> Vec>; } -impl RectHelper for Rect { - fn fit_into(&self, bounds: &Rect) -> Self { +impl RectHelper for Rect { + fn fit_into(self, bounds: Self) -> Self { let content_aspect_ratio = self.w() / self.h(); let bounds_aspect_ratio = bounds.w() / bounds.h(); @@ -43,11 +46,11 @@ impl RectHelper for Rect { } } - fn bottom_half(&self) -> Self { + fn bottom_half(self) -> Self { Self::from_corners(self.mid_left(), self.bottom_right()) } - fn bottom_part(&self, part: f32) -> Self { + fn bottom_part(self, part: f32) -> Self { debug_assert!((0f32..=1f32).contains(&part)); Self::from_corner_points( [self.left(), self.bottom()], @@ -55,11 +58,11 @@ impl RectHelper for Rect { ) } - fn top_half(&self) -> Self { + fn top_half(self) -> Self { Self::from_corners(self.top_left(), self.mid_right()) } - fn top_part(&self, part: f32) -> Self { + fn top_part(self, part: f32) -> Self { debug_assert!((0f32..=1f32).contains(&part)); Self::from_corner_points( [self.left(), self.top()], @@ -67,11 +70,11 @@ impl RectHelper for Rect { ) } - fn left_half(&self) -> Self { + fn left_half(self) -> Self { Self::from_corners(self.top_left(), self.mid_bottom()) } - fn left_part(&self, part: f32) -> Self { + fn left_part(self, part: f32) -> Self { debug_assert!((0f32..=1f32).contains(&part)); Self::from_corner_points( [self.left(), self.bottom()], @@ -79,11 +82,11 @@ impl RectHelper for Rect { ) } - fn right_half(&self) -> Self { - Self::from_corners(self.top_left(), self.mid_bottom()) + fn right_half(self) -> Self { + Self::from_corners(self.mid_top(), self.bottom_right()) } - fn right_part(&self, part: f32) -> Self { + fn right_part(self, part: f32) -> Self { debug_assert!((0f32..=1f32).contains(&part)); Self::from_corner_points( [self.right(), self.bottom()], @@ -91,8 +94,16 @@ impl RectHelper for Rect { ) } + fn part_horizontal(self, part: f32) -> [Self; 2] { + [self.left_part(part), self.right_part(1f32 - part)] + } + + fn part_vertical(self, part: f32) -> [Self; 2] { + [self.bottom_part(part), self.top_part(1f32 - part)] + } + #[allow(clippy::cast_precision_loss)] - fn split_horizontal(&self) -> [Self; N] { + fn split_horizontal(self) -> [Self; N] { let mut subdivisions = [unsafe { std::mem::zeroed() }; N]; let subdivided_width = self.w() / N as f32; @@ -108,7 +119,7 @@ impl RectHelper for Rect { } #[allow(clippy::cast_precision_loss)] - fn split_horizontal_vec(&self, num: usize) -> Vec { + fn split_horizontal_vec(self, num: usize) -> Vec { let mut subdivisions = Vec::with_capacity(num); let subdivided_width = self.w() / num as f32; @@ -124,7 +135,7 @@ impl RectHelper for Rect { } #[allow(clippy::cast_precision_loss)] - fn split_vertical(&self) -> [Self; N] { + fn split_vertical(self) -> [Self; N] { let mut subdivisions = [unsafe { std::mem::zeroed() }; N]; let subdivided_height = self.h() / N as f32; @@ -140,7 +151,7 @@ impl RectHelper for Rect { } #[allow(clippy::cast_precision_loss)] - fn split_vertical_vec(&self, num: usize) -> Vec { + fn split_vertical_vec(self, num: usize) -> Vec { let mut subdivisions = Vec::with_capacity(num); let subdivided_height = self.h() / num as f32;