diff --git a/src/formatter.rs b/src/formatter.rs index fbbf577..d2d5afa 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -1,5 +1,5 @@ pub fn format_squares_numbers(moon_squares: u32, sun_squares: u32) -> String { let moon_formatted = format!("{:03}", moon_squares); let sun_formatted = format!("{:03}", sun_squares); - format!("Moon {} | Sun {}", moon_formatted, sun_formatted) -} + format!("{} | {}", moon_formatted, sun_formatted) +} \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index a8f2f24..8f652be 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,36 +1,51 @@ +use std::fmt::Alignment; + +use ggez::mint::Point2; +use ggez::timer; use ggez::{graphics, Context, GameResult, event::EventHandler}; -use ggez::graphics::{Text, TextFragment, Color as GgezColor}; +use ggez::graphics::{Color, Drawable, Text, TextFragment}; use nalgebra::Vector2; use rand::Rng; -use crate::utils::count_objects; +use crate::utils::{count_objects, lerp_color}; use crate::object::{Object, Team, ObjectKind}; use crate::formatter::format_squares_numbers; -pub mod game_constants { +pub mod game_constants { pub const ROW_SIZE: i32 = 20; pub const COLUMN_SIZE: i32 = 10; pub const CIRCLE_SIZE: f32 = 25.0; pub const SQUARE_SIZE: f32 = 50.0; - pub const FIELD_START_Y: f32 = 100.0; - pub const FIELD_START_X: f32 = 128.0; - pub const FIELD_WIDTH: f32 = (SQUARE_SIZE * 2.0) * COLUMN_SIZE as f32; pub const FIELD_HEIGHT: f32 = (SQUARE_SIZE) * ROW_SIZE as f32; pub const MOVEMENT_SPEED: f32 = 15.0; } + pub struct Game { + window_width: f32, + window_height: f32, + scale_factor: f32, + bounds: (f32, f32), + squares: Vec, sun_circle: Object, moon_circle: Object, + + background_color: Color, + target_background_color: Color, + transition_duration: f32, + transition_timer: f32, } impl Game { pub fn new(ctx: &mut Context) -> Self { + let (window_width, window_height) = ctx.gfx.drawable_size(); + let scale_factor = window_width / game_constants::FIELD_WIDTH; + let mut rng = rand::thread_rng(); let moon_x_sign = if rng.gen::() { 1.0 } else { -1.0 }; @@ -39,35 +54,55 @@ impl Game { let sun_x_sign = if rng.gen::() { 1.0 } else { -1.0 }; let sun_y_sign = if rng.gen::() { 1.0 } else { -1.0 }; + let moon_pos = (window_width * 0.7, window_height * 0.6); + let sun_pos = (window_width * 0.35, window_height * 0.6); + + let field_start_x: f32 = (window_width - game_constants::FIELD_WIDTH) / 2.0; + let field_start_y: f32 = (window_height - game_constants::FIELD_HEIGHT) / 2.0; + + let bounds = (field_start_x, field_start_y); + + let background_color = Color::from_rgb(0xF7, 0xC5, 0x9F); + let target_background_color = Color::from_rgb(0x2A, 0x32, 0x4B); + let transition_duration = 2.0; + let transition_timer = 0.0; + let moon_circle = Object { - position: (870.0, 650.0), + position: moon_pos, team: Team::MOON, kind: ObjectKind::Circle, direction: Vector2::new( moon_x_sign * 1.5 * game_constants::MOVEMENT_SPEED, moon_y_sign * 1.5 * game_constants::MOVEMENT_SPEED - ) + ), + }; let sun_circle = Object { - position: (385.5, 650.0), + position: sun_pos, team: Team::SUN, kind: ObjectKind::Circle, direction: Vector2::new( sun_x_sign * 1.5 * game_constants::MOVEMENT_SPEED, sun_y_sign * 1.5 * game_constants::MOVEMENT_SPEED - ) + ), + }; + let mut squares = Vec::new(); for row in 0..game_constants::ROW_SIZE { for column in 0..game_constants::COLUMN_SIZE { squares.push(Object { - position: (game_constants::FIELD_START_X + (column as f32) * game_constants::SQUARE_SIZE, game_constants::FIELD_START_Y + (row as f32) * game_constants::SQUARE_SIZE), + position: ( + (field_start_x + (column as f32) * game_constants::SQUARE_SIZE), + (field_start_y + (row as f32) * game_constants::SQUARE_SIZE), + ), team: Team::MOON, kind: ObjectKind::Square, direction: Vector2::new(0.0, 0.0), + }); } } @@ -75,10 +110,14 @@ impl Game { for row in 0..game_constants::ROW_SIZE { for column in 0..game_constants::COLUMN_SIZE { squares.push(Object { - position: (game_constants::FIELD_START_X + (column as f32) * game_constants::SQUARE_SIZE + (game_constants::COLUMN_SIZE as f32) * game_constants::SQUARE_SIZE, game_constants::FIELD_START_Y + (row as f32) * game_constants::SQUARE_SIZE), + position: ( + (field_start_x + (column as f32) * game_constants::SQUARE_SIZE + (game_constants::COLUMN_SIZE as f32) * game_constants::SQUARE_SIZE), + (field_start_y + (row as f32) * game_constants::SQUARE_SIZE), + ), team: Team::SUN, kind: ObjectKind::Square, direction: Vector2::new(0.0, 0.0), + }); } } @@ -94,52 +133,102 @@ impl Game { } Game { + window_height, + window_width, + scale_factor, + bounds, + squares, sun_circle, moon_circle, + + background_color, + target_background_color, + transition_duration, + transition_timer, } } + + fn update_background_color(&mut self, dt: f32) { + if self.transition_timer < self.transition_duration { + self.transition_timer += dt; + let t = self.transition_timer / self.transition_duration; + self.background_color = lerp_color(self.background_color, self.target_background_color, t); + } + } + + fn set_target_background_color(&mut self, target_color: Color, duration: f32) { + self.target_background_color = target_color; + self.transition_duration = duration; + self.transition_timer = 0.0; + } } impl EventHandler for Game { - fn update(&mut self, _: &mut Context) -> GameResult { + fn update(&mut self, ctx: &mut Context) -> GameResult { self.sun_circle.update_position(); self.moon_circle.update_position(); - self.sun_circle.handle_boundary_collision(); - self.moon_circle.handle_boundary_collision(); + + self.sun_circle.handle_boundary_collision(self.bounds); + self.moon_circle.handle_boundary_collision(self.bounds); for square in &mut self.squares { self.sun_circle.handle_collision(square); self.moon_circle.handle_collision(square); } + let dt = ctx.time.delta().as_secs_f32(); + self.update_background_color(dt); + Ok(()) } fn draw(&mut self, ctx: &mut Context) -> GameResult { - let canvas_color = GgezColor::from_rgb_u32(0x767B91); - let mut canvas = graphics::Canvas::from_frame(ctx, canvas_color); + let captured = count_objects(&self.squares, Team::MOON); - let dest_point = ggez::glam::Vec2::new(428.0, 1180.0); - let font_color = GgezColor::from_rgb_u32(0xE1E5EE); - let text_fragment = TextFragment::new(format_squares_numbers(count_objects(&self.squares, Team::MOON), count_objects(&self.squares, Team::SUN))) - .font("LiberationMono") - .scale(40.0) - .color(font_color); - canvas.draw( - &Text::new(text_fragment), - dest_point, - ); + let max_cells = game_constants::ROW_SIZE * game_constants::COLUMN_SIZE; - for square in &mut self.squares { - square.draw(ctx, &mut canvas)?; - } + let darkness_level = captured as f32 / max_cells as f32; + + let start_color = [0xF7, 0xC5, 0x9F]; + let end_color = [0x2A, 0x32, 0x4B]; + + let interpolated_color = [ + (start_color[0] as f32 + (end_color[0] as f32 - start_color[0] as f32) * darkness_level) as u8, + (start_color[1] as f32 + (end_color[1] as f32 - start_color[1] as f32) * darkness_level) as u8, + (start_color[2] as f32 + (end_color[2] as f32 - start_color[2] as f32) * darkness_level) as u8, + ]; + + let canvas_color = Color::from_rgb(interpolated_color[0], interpolated_color[1], interpolated_color[2]); + let mut canvas = graphics::Canvas::from_frame(ctx, canvas_color); + + for i in 0..self.squares.len() { + self.squares[i].draw(ctx, &mut canvas)?; + } + self.sun_circle.draw(ctx, &mut canvas)?; self.moon_circle.draw(ctx, &mut canvas)?; - + + let font_color = Color::from_rgb_u32(0xE1E5EE); + let text_fragment = TextFragment::new(format_squares_numbers(count_objects(&self.squares, Team::MOON), count_objects(&self.squares, Team::SUN))) + .font("LiberationMono") + .scale(40.0 * self.scale_factor) + .color(font_color); + + let text = Text::new(text_fragment); + let text_dimensions = text.measure(ctx)?; + + let x = (self.window_width - text_dimensions.x) / 2.0; + let y = self.window_height - text_dimensions.y; + + let dest_point = ggez::glam::Vec2::new(x, y); + + canvas.draw(&text, dest_point); + canvas.finish(ctx)?; - + Ok(()) } -} \ No newline at end of file + +} diff --git a/src/main.rs b/src/main.rs index d597ab9..c768cd7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,9 +18,13 @@ fn main() { }; let result = ContextBuilder::new("Moon & Sun", "wedyarit") - .window_mode(conf::WindowMode::default().dimensions(1280.0, 1280.0)) + .window_mode(conf::WindowMode::default() + .maximized(true) + .resizable(false)) .add_resource_path(resource_dir) - .window_setup(conf::WindowSetup::default().title("Moon & Sun").icon("/icon.png")) + .window_setup(conf::WindowSetup::default() + .title("Moon & Sun") + .icon("/icon.png")) .build(); let (mut ctx, event_loop) = match result { Ok((ctx, event_loop)) => (ctx, event_loop), diff --git a/src/object.rs b/src/object.rs index e9c367e..d9bb0ab 100644 --- a/src/object.rs +++ b/src/object.rs @@ -3,7 +3,7 @@ use ggez::{Context, GameResult}; use ggez::glam::*; use nalgebra::Vector2; -use crate::game::{game_constants}; +use crate::game::game_constants; pub enum ObjectKind { Circle, @@ -52,15 +52,15 @@ impl Object { } } - pub fn handle_boundary_collision(&mut self) { + pub fn handle_boundary_collision(&mut self, bounds: (f32, f32)) { let (mut x, mut y) = self.position; - if x - game_constants::CIRCLE_SIZE <= game_constants::FIELD_START_X || x + game_constants::CIRCLE_SIZE >= game_constants::FIELD_START_X + game_constants::FIELD_WIDTH { + if x - game_constants::CIRCLE_SIZE <= bounds.0 || x + game_constants::CIRCLE_SIZE >= bounds.0 + game_constants::FIELD_WIDTH { self.direction.x *= -1.0; x += self.direction.x; } - if y - game_constants::CIRCLE_SIZE <= game_constants::FIELD_START_Y || y + game_constants::CIRCLE_SIZE >= game_constants::FIELD_START_Y + game_constants::FIELD_HEIGHT { + if y - game_constants::CIRCLE_SIZE <= bounds.1 || y + game_constants::CIRCLE_SIZE >= bounds.1 + game_constants::FIELD_HEIGHT { self.direction.y *= -1.0; y += self.direction.y; } diff --git a/src/utils.rs b/src/utils.rs index 02ad727..988e924 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,5 @@ +use ggez::graphics::Color; + use crate::object::{Object, Team}; pub fn count_objects(objects: &Vec, team: Team) -> u32 { @@ -8,4 +10,12 @@ pub fn count_objects(objects: &Vec, team: Team) -> u32 { } } count +} + +pub fn lerp_color(color1: Color, color2: Color, t: f32) -> Color { + let r = (color1.r * (1.0 - t) + color2.r * t).round() as u8; + let g = (color1.g * (1.0 - t) + color2.g * t).round() as u8; + let b = (color1.b * (1.0 - t) + color2.b * t).round() as u8; + let a = (color1.a * (1.0 - t) + color2.a * t).round() as u8; + Color::from_rgba(r, g, b, a) } \ No newline at end of file