diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cell.rs | 229 | ||||
-rw-r--r-- | src/constants.rs | 43 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 307 |
4 files changed, 470 insertions, 111 deletions
diff --git a/src/cell.rs b/src/cell.rs index b085c5e..e4be4ac 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -1,143 +1,188 @@ use crate::constants; -use ggez::graphics::{ - self, spritebatch::SpriteBatch, Color, DrawMode, DrawParam, Mesh, Rect, StrokeOptions, -}; -use ggez::mint::{Point2, Vector2}; -use ggez::{Context, GameResult}; +use bevy::prelude::*; use rand::{ distributions::{Distribution, Standard}, Rng, }; -use std::time::Instant; -#[derive(Clone, Copy, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum Occupant { None, Green, Yellow, - Diamond, Red, - Explosion { frame: usize, timer: Instant }, + Blue, + Purple, + Explosion, +} + +impl Default for Occupant { + fn default() -> Occupant { + Occupant::None + } +} + +impl Occupant { + pub fn to_index(&self) -> u32 { + match self { + Occupant::Green => constants::TILESHEET_GREEN, + Occupant::Yellow => constants::TILESHEET_YELLOW, + Occupant::Red => constants::TILESHEET_RED, + Occupant::Blue => constants::TILESHEET_BLUE, + Occupant::Purple => constants::TILESHEET_PURPLE, + Occupant::Explosion => constants::TILESHEET_EXPLOSION1, + Occupant::None => 0, + } + } } impl Distribution<Occupant> for Standard { fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Occupant { - match rng.gen_range(0..=3) { + match rng.gen_range(0..=4) { 0 => Occupant::Green, 1 => Occupant::Yellow, - 2 => Occupant::Diamond, + 2 => Occupant::Blue, 3 => Occupant::Red, + 4 => Occupant::Purple, _ => Occupant::None, } } } -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Default, Copy)] pub struct Cell { + pub x: usize, + pub y: usize, pub occupant: Occupant, - position: Point2<f32>, - hover: bool, - clicked: bool, + pub selected: bool, + pub hovered: bool, } impl Cell { - pub fn new(position: Point2<f32>) -> Cell { + pub fn new(x: usize, y: usize) -> Cell { Cell { - occupant: rand::random(), - position, - hover: false, - clicked: false, + x, + y, + occupant: Occupant::None, + selected: false, + hovered: false, + } + } + + pub fn set_occupant( + &mut self, + occupant: Occupant, + sprite: &mut TextureAtlasSprite, + visible: &mut Visible, + ) { + self.occupant = occupant; + sprite.index = self.occupant.to_index(); + if self.occupant == Occupant::None { + visible.is_visible = false; + } else { + visible.is_visible = true; } } +} - pub fn contains(&self, position: Point2<f32>) -> bool { - position.x > self.position.x - && position.y > self.position.y - && position.x < self.position.x + constants::TILE_WIDTH * constants::TILE_SCALE - && position.y < self.position.y + constants::TILE_WIDTH * constants::TILE_SCALE +pub fn insert(mut q: Query<(&mut Cell, &mut TextureAtlasSprite, &mut Visible)>) { + for (mut cell, mut sprite, mut visible) in q.iter_mut() { + if cell.occupant == Occupant::None && cell.y == constants::GRID_SIZE - 1 { + cell.set_occupant(rand::random(), &mut sprite, &mut visible); + } } +} - pub fn moveable(&self) -> bool { - //!matches!(self.occupant, Occupant::Explosion{ ..} | Occupant::None) - match self.occupant { - Occupant::Explosion { .. } => false, - Occupant::None => false, - _ => true, +pub fn start_explosion(mut commands: Commands, mut q: Query<(Entity, &Cell), Without<Timer>>) { + for (entity, cell) in q.iter_mut() { + if cell.occupant == Occupant::Explosion { + commands + .entity(entity) + .insert(Timer::from_seconds(0.1, true)); } } +} - pub fn clicked_on(&mut self) { - self.clicked = true; +pub fn check(mut q: Query<(&mut Cell, &mut TextureAtlasSprite, &mut Visible)>) { + let mut cells = [[Cell::default(); constants::GRID_SIZE]; constants::GRID_SIZE]; + for (cell, _, _) in q.iter_mut() { + cells[cell.x][cell.y] = *cell; } - pub fn clicked_off(&mut self) { - self.clicked = false; + let mut last = Occupant::None; + let mut connected = Vec::new(); + + for (i, row) in cells.iter().enumerate() { + let mut c = Vec::new(); + for (j, _) in row.iter().enumerate() { + if cells[i][j].occupant == last && last != Occupant::None && last != Occupant::Explosion + { + c.push((i, j)); + c.push((i, j - 1)); + } else { + connected.push(c.clone()); + c.clear(); + } + last = cells[i][j].occupant; + } + connected.push(c); + last = Occupant::None; } - pub fn hover_on(&mut self) { - self.hover = true; + for (i, row) in cells.iter().enumerate() { + let mut c = Vec::new(); + for (j, _) in row.iter().enumerate() { + if cells[j][i].occupant == last && last != Occupant::None && last != Occupant::Explosion + { + c.push((j, i)); + c.push((j - 1, i)); + } else { + connected.push(c.clone()); + c.clear(); + } + last = cells[j][i].occupant; + } + connected.push(c); + last = Occupant::None; } - pub fn hover_off(&mut self) { - self.hover = false; + connected = connected.into_iter().filter(|c| c.len() > 4).collect(); + + for c in connected.iter() { + for (i, j) in c.iter() { + for (mut cell, mut sprite, mut visible) in q.iter_mut() { + if &cell.x == i && &cell.y == j && cell.occupant != Occupant::Explosion { + cell.set_occupant(Occupant::Explosion, &mut sprite, &mut visible); + } + } + } } +} - pub fn draw(&self, context: &mut Context, spritebatch: &mut SpriteBatch) -> GameResult { - let source = match self.occupant { - Occupant::None => None, - Occupant::Explosion { frame, .. } => match frame { - 0 => Some(Rect::new(0.0, 0.5, 0.25, 0.5)), - 1 => Some(Rect::new(0.25, 0.5, 0.25, 0.5)), - 2 => Some(Rect::new(0.50, 0.5, 0.25, 0.5)), - 3 => Some(Rect::new(0.75, 0.5, 0.25, 0.5)), - _ => None, - }, - Occupant::Green => Some(Rect::new(0.0, 0.0, 0.25, 0.5)), - Occupant::Yellow => Some(Rect::new(0.25, 0.0, 0.25, 0.5)), - Occupant::Diamond => Some(Rect::new(0.50, 0.0, 0.25, 0.5)), - Occupant::Red => Some(Rect::new(0.75, 0.0, 0.25, 0.5)), - }; - - if let Some(source) = source { - spritebatch.add( - DrawParam::default() - .src(source) - .dest(self.position) - .scale(Vector2 { - x: constants::TILE_SCALE, - y: constants::TILE_SCALE, - }), - ); +pub fn falling(mut q: Query<(&mut Cell, &mut TextureAtlasSprite, &mut Visible)>) { + let mut have_gems = Vec::new(); + for (cell, _sprite, _visible) in q.iter_mut() { + if cell.occupant != Occupant::None { + have_gems.push(*cell); } + } - if self.hover { - let mesh = Mesh::new_rectangle( - context, - DrawMode::Stroke(StrokeOptions::default()), - Rect::new( - self.position.x, - self.position.y, - constants::TILE_WIDTH * constants::TILE_SCALE, - constants::TILE_HEIGHT * constants::TILE_SCALE, - ), - Color::from_rgb(255, 100, 100), - )?; - graphics::draw(context, &mesh, DrawParam::default())?; + let mut moved_gems = Vec::new(); + for (mut cell, mut sprite, mut visible) in q.iter_mut() { + if cell.occupant == Occupant::None { + if let Some(c) = have_gems + .iter() + .find(|&c| (c.x, c.y) == (cell.x, cell.y + 1)) + { + cell.set_occupant(c.occupant, &mut sprite, &mut visible); + moved_gems.push(c); + } } - if self.clicked { - let mesh = Mesh::new_rectangle( - context, - DrawMode::Stroke(StrokeOptions::default()), - Rect::new( - self.position.x, - self.position.y, - constants::TILE_WIDTH * constants::TILE_SCALE, - constants::TILE_HEIGHT * constants::TILE_SCALE, - ), - Color::from_rgb(100, 255, 100), - )?; - graphics::draw(context, &mesh, DrawParam::default())?; + } + + for (mut cell, mut sprite, mut visible) in q.iter_mut() { + if moved_gems.iter().any(|c| (c.x, c.y) == (cell.x, cell.y)) { + cell.set_occupant(Occupant::None, &mut sprite, &mut visible); } - Ok(()) } } diff --git a/src/constants.rs b/src/constants.rs index f9a5003..86bbe5e 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,8 +1,35 @@ -pub const TILE_SCALE: f32 = 3.0; -pub const TILE_HEIGHT: f32 = 16.0; -pub const TILE_WIDTH: f32 = 16.0; -pub const BORDER_SIZE: f32 = 3.0; -pub const SHIFT_X: f32 = 50.0; -pub const SHIFT_Y: f32 = 50.0; -pub const COLUMNS: usize = 8; -pub const ROWS: usize = 8; +pub const TILE_SCALE: f32 = 3.5; +pub const TILE_SIZE: f32 = 16.0; +pub const WINDOW_HEIGHT: f32 = 600.0; +pub const WINDOW_WIDTH: f32 = 800.0; +pub const GRID_SIZE: usize = 8; + +pub const TILESHEET_EXPLOSION1: u32 = 0; +pub const TILESHEET_EXPLOSION2: u32 = 1; +pub const TILESHEET_EXPLOSION3: u32 = 2; +pub const TILESHEET_EXPLOSION4: u32 = 3; +pub const TILESHEET_EXPLOSION5: u32 = 4; + +pub const TILESHEET_GREEN: u32 = 5; +pub const TILESHEET_YELLOW: u32 = 6; +pub const TILESHEET_BLUE: u32 = 7; +pub const TILESHEET_RED: u32 = 8; +pub const TILESHEET_PURPLE: u32 = 9; + +pub const TILESHEET_STAR1: u32 = 10; +pub const TILESHEET_STAR2: u32 = 11; +pub const TILESHEET_STAR3: u32 = 12; +//pub const TILESHEET_X: u32 = 13; +//pub const TILESHEET_X: u32 = 14; + +pub const TILESHEET_COSMONAUT1: u32 = 15; +pub const TILESHEET_VISOR1: u32 = 16; +pub const TILESHEET_VISOR2: u32 = 17; +//pub const TILESHEET_X: u32 = 18; +//pub const TILESHEET_X: u32 = 19; + +pub const TILESHEET_COSMONAUT2: u32 = 20; +pub const TILESHEET_VISOR3: u32 = 21; +pub const TILESHEET_VISOR4: u32 = 22; +//pub const TILESHEET_X: u32 = 23; +//pub const TILESHEET_X: u32 = 24; @@ -1,4 +1,2 @@ pub mod cell; pub mod constants; -pub mod cosmonaut; -pub mod game; diff --git a/src/main.rs b/src/main.rs index a1fee19..c69d791 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,301 @@ -use ggez::event::{self}; -use ggez::{ContextBuilder, GameResult}; +use bevy::math::Vec3; +use bevy::prelude::*; +use gems::cell::{self, Cell, Occupant}; +use gems::constants; +use rand::{thread_rng, Rng}; +use std::time::Duration; -use gems::game::Game; +fn star_spawning(mut commands: Commands, time: Res<Time>, mut q: Query<&mut Timer>) { + for mut timer in q.iter_mut() { + if !timer.repeating() { + timer.tick(time.delta()); + if timer.just_finished() { + timer.set_duration(Duration::new(thread_rng().gen_range(2..5), 0)); + timer.reset(); + commands + .spawn_bundle(SpriteSheetBundle { + sprite: TextureAtlasSprite::new(constants::TILESHEET_STAR1), + transform: Transform { + translation: Vec3::new( + thread_rng().gen_range(-300..300) as f32, + thread_rng().gen_range(-200..300) as f32, + 0.0, + ), + scale: Vec3::splat(thread_rng().gen_range(1..3) as f32), + ..Default::default() + }, + ..Default::default() + }) + .insert(Timer::from_seconds(0.25, true)); + } + } + } +} + +pub fn setup( + mut commands: Commands, + asset_server: Res<AssetServer>, + mut materials: ResMut<Assets<ColorMaterial>>, + mut texture_atlases: ResMut<Assets<TextureAtlas>>, +) { + let background = asset_server.load("background.png"); + let tileset = asset_server.load("tileset.png"); + let title = asset_server.load("title.png"); + + let atlas = TextureAtlas::from_grid( + tileset, + Vec2::new(constants::TILE_SIZE, constants::TILE_SIZE), + 5, + 5, + ); + let atlas_handle = texture_atlases.add(atlas); + + commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(SpriteBundle { + material: materials.add(background.into()), + transform: Transform { + translation: Vec3::new(50.0, 0.0, 0.0), + scale: Vec3::splat(constants::TILE_SCALE), + ..Default::default() + }, + ..Default::default() + }); + commands.spawn_bundle(SpriteBundle { + material: materials.add(title.into()), + transform: Transform { + translation: Vec3::new(240.0, 200.0, 0.0), + scale: Vec3::splat(2.0), + ..Default::default() + }, + ..Default::default() + }); + commands.spawn_bundle(SpriteSheetBundle { + sprite: TextureAtlasSprite::new(constants::TILESHEET_COSMONAUT1), + texture_atlas: atlas_handle.clone(), + transform: Transform { + translation: Vec3::new(225.0, -200.0, 0.0), + scale: Vec3::splat(constants::TILE_SCALE), + ..Default::default() + }, + ..Default::default() + }); + commands.spawn_bundle((Timer::from_seconds(1.0, false),)); + commands.spawn_bundle(SpriteSheetBundle { + sprite: TextureAtlasSprite::new(constants::TILESHEET_COSMONAUT2), + texture_atlas: atlas_handle.clone(), + transform: Transform { + translation: Vec3::new( + 225.0, + -200.0 + (-constants::TILE_SIZE) * constants::TILE_SCALE, + 0.0, + ), + scale: Vec3::splat(constants::TILE_SCALE), + ..Default::default() + }, + ..Default::default() + }); + + for i in 0..constants::GRID_SIZE { + for j in 0..constants::GRID_SIZE { + commands + .spawn_bundle(SpriteSheetBundle { + texture_atlas: atlas_handle.clone(), + visible: Visible { + is_visible: false, + is_transparent: true, + }, + transform: Transform { + translation: Vec3::new( + ((i as f32) * constants::TILE_SIZE * constants::TILE_SCALE) - 330.0, + ((j as f32) * constants::TILE_SIZE * constants::TILE_SCALE) - 160.0, + 1.0, + ), + scale: Vec3::splat(constants::TILE_SCALE), + ..Default::default() + }, + ..Default::default() + }) + .insert(Cell::new(i, j)); + } + } +} + +fn animation( + mut commands: Commands, + time: Res<Time>, + mut q: Query<( + Entity, + Option<&mut Cell>, + &mut Timer, + &mut TextureAtlasSprite, + &mut Visible, + )>, +) { + for (entity, cell, mut timer, mut sprite, mut visible) in q.iter_mut() { + timer.tick(time.delta()); + if timer.finished() { + let index = match sprite.index { + constants::TILESHEET_EXPLOSION1 => Some(constants::TILESHEET_EXPLOSION2), + constants::TILESHEET_EXPLOSION2 => Some(constants::TILESHEET_EXPLOSION3), + constants::TILESHEET_EXPLOSION3 => Some(constants::TILESHEET_EXPLOSION4), + constants::TILESHEET_EXPLOSION4 => Some(constants::TILESHEET_EXPLOSION5), + + constants::TILESHEET_VISOR1 => Some(constants::TILESHEET_VISOR2), + constants::TILESHEET_VISOR2 => Some(constants::TILESHEET_VISOR3), + constants::TILESHEET_VISOR3 => Some(constants::TILESHEET_VISOR4), + + constants::TILESHEET_STAR1 => Some(constants::TILESHEET_STAR2), + constants::TILESHEET_STAR2 => Some(constants::TILESHEET_STAR3), + + _ => None, + }; + + if let Some(index) = index { + sprite.index = index; + } else if let Some(mut cell) = cell { + cell.set_occupant(Occupant::None, &mut sprite, &mut visible); + commands.entity(entity).remove::<Timer>(); + } else { + commands.entity(entity).despawn(); + } + } + } +} + +fn cosmonaut_detect( + mut commands: Commands, + windows: Res<Windows>, + mut q: Query<(&Transform, &TextureAtlasSprite)>, +) { + if let Some(mut cursor_position) = windows + .get_primary() + .and_then(|window| window.cursor_position()) + { + cursor_position.x -= + (constants::WINDOW_WIDTH / 2.0) - constants::TILE_SIZE * constants::TILE_SCALE * 0.5; + cursor_position.y -= + (constants::WINDOW_HEIGHT / 2.0) - constants::TILE_SIZE * constants::TILE_SCALE * 0.5; + for (transform, sprite) in q.iter_mut() { + if transform.translation.x < cursor_position.x + && transform.translation.x + constants::TILE_SIZE * constants::TILE_SCALE + > cursor_position.x + && transform.translation.y < cursor_position.y + && transform.translation.y + constants::TILE_SIZE * constants::TILE_SCALE + > cursor_position.y + && sprite.index == constants::TILESHEET_COSMONAUT1 + { + commands + .spawn_bundle(SpriteSheetBundle { + sprite: TextureAtlasSprite::new(constants::TILESHEET_VISOR1), + transform: *transform, + ..Default::default() + }) + .insert(Timer::from_seconds(0.1, true)); + } + } + } +} + +fn mouse( + windows: Res<Windows>, + mut q: Query<(&mut Cell, &mut Transform, &mut TextureAtlasSprite)>, + mouse_button_input: Res<Input<MouseButton>>, +) { + if let Some(mut cursor_position) = windows + .get_primary() + .and_then(|window| window.cursor_position()) + { + cursor_position.x -= + (constants::WINDOW_WIDTH / 2.0) - constants::TILE_SIZE * constants::TILE_SCALE * 0.5; + cursor_position.y -= + (constants::WINDOW_HEIGHT / 2.0) - constants::TILE_SIZE * constants::TILE_SCALE * 0.5; + + for (cell, _, mut sprite) in q.iter_mut() { + if cell.selected { + sprite.color.set_a(0.2); + } else if cell.hovered { + sprite.color.set_a(0.5); + } else { + sprite.color.set_a(1.0); + } + } + + for (mut cell, transform, _) in q.iter_mut() { + if transform.translation.x < cursor_position.x + && transform.translation.x + constants::TILE_SIZE * constants::TILE_SCALE + > cursor_position.x + && transform.translation.y < cursor_position.y + && transform.translation.y + constants::TILE_SIZE * constants::TILE_SCALE + > cursor_position.y + { + cell.hovered = true; + if mouse_button_input.just_pressed(MouseButton::Left) { + cell.selected = true; + } + } else { + cell.hovered = false; + } + } + + if mouse_button_input.just_released(MouseButton::Left) { + let mut cells: Vec<Cell> = Vec::new(); + for (cell, _, _) in q.iter_mut() { + cells.push(*cell); + } + if let Some(mut selected) = cells.clone().iter_mut().find(|c| c.selected) { + if let Some(mut hovered) = cells.iter_mut().find(|c| c.hovered) { + if (selected.x == hovered.x + 1 && selected.y == hovered.y) + || (selected.x == hovered.x.overflowing_sub(1).0 && selected.y == hovered.y) + || (selected.y == hovered.y + 1 && selected.x == hovered.x) + || (selected.y == hovered.y.overflowing_sub(1).0 && selected.x == hovered.x) + { + let tmp = selected.occupant; + selected.occupant = hovered.occupant; + hovered.occupant = tmp; + for (mut cell, _, mut sprite) in q.iter_mut() { + if cell.x == selected.x && cell.y == selected.y { + cell.occupant = selected.occupant; + sprite.index = cell.occupant.to_index(); + } else if cell.x == hovered.x && cell.y == hovered.y { + cell.occupant = hovered.occupant; + sprite.index = cell.occupant.to_index(); + } + } + } + } + } + for (mut cell, _, _) in q.iter_mut() { + cell.selected = false; + } + } + } +} + +pub struct GemsPlugin; +impl Plugin for GemsPlugin { + fn build(&self, app: &mut AppBuilder) { + app.add_startup_system(setup.system()); + app.add_system(cell::insert.system()); + app.add_system(cell::falling.system()); + app.add_system(cell::check.system()); + app.add_system(cell::start_explosion.system()); + app.add_system(mouse.system()); + app.add_system(cosmonaut_detect.system()); + app.add_system(animation.system()); + app.add_system(star_spawning.system()); + } +} -pub fn main() -> GameResult { - let (ref mut context, ref mut event_loop) = ContextBuilder::new("gems", "jw&tb") - .add_resource_path("./resources") - .build()?; - let game = &mut Game::new(context)?; - event::run(context, event_loop, game) +pub fn main() { + App::build() + .insert_resource(WindowDescriptor { + title: "gems".to_string(), + width: constants::WINDOW_WIDTH, + height: constants::WINDOW_HEIGHT, + resizable: false, + ..Default::default() + }) + .add_plugins(DefaultPlugins) + .add_plugin(GemsPlugin) + .run(); } |