use ggez::event::{self, EventHandler}; use ggez::graphics::{ self, spritebatch::SpriteBatch, Color, DrawMode, DrawParam, FilterMode, Image, Mesh, Rect, StrokeOptions, WrapMode, }; use ggez::input::mouse; use ggez::mint::{Point2, Vector2}; use ggez::{Context, ContextBuilder, GameResult}; use rand::{ distributions::{Distribution, Standard}, Rng, }; use std::time::Instant; 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; #[derive(Clone, Copy, PartialEq)] enum Occupant { None, Green, Yellow, Diamond, Red, Explosion { frame: usize, timer: Instant }, } impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Occupant { match rng.gen_range(0..=3) { 0 => Occupant::Green, 1 => Occupant::Yellow, 2 => Occupant::Diamond, 3 => Occupant::Red, _ => Occupant::None, } } } #[derive(Clone, Copy)] enum CosmonautFrames { None, One, Two, Three, Four, } impl CosmonautFrames { pub fn next(&mut self) { *self = match self { CosmonautFrames::None => CosmonautFrames::One, CosmonautFrames::One => CosmonautFrames::Two, CosmonautFrames::Two => CosmonautFrames::Three, CosmonautFrames::Three => CosmonautFrames::Four, CosmonautFrames::Four => CosmonautFrames::None, }; } } struct Cosmonaut { destination: Point2, image: Image, frame: CosmonautFrames, timer: Instant, scale: Vector2, } impl Cosmonaut { pub fn new(context: &mut Context) -> GameResult { let mut image = Image::new(context, "/cosmonaut.png")?; image.set_filter(FilterMode::Nearest); Ok(Cosmonaut { image, destination: Point2 { x: 600.0, y: 200.0 }, frame: CosmonautFrames::None, timer: Instant::now(), scale: Vector2 { x: TILE_SCALE * 2.0, y: TILE_SCALE * 2.0, }, }) } pub fn draw(&self, context: &mut Context) -> GameResult { graphics::draw( context, &self.image, DrawParam::default() .dest(self.destination) .scale(self.scale) .src(Rect::new(0.0, 0.0, 1.0 / 3.0, 1.0)), )?; let source = match self.frame { CosmonautFrames::None => None, CosmonautFrames::One => Some(Rect::new(1.0 / 3.0, 0.0, 1.0 / 3.0, 1.0 / 2.0)), CosmonautFrames::Two => Some(Rect::new(2.0 / 3.0, 0.0, 1.0, 1.0 / 2.0)), CosmonautFrames::Three => Some(Rect::new(1.0 / 3.0, 1.0 / 2.0, 1.0 / 3.0, 1.0 / 2.0)), CosmonautFrames::Four => Some(Rect::new(2.0 / 3.0, 1.0 / 2.0, 1.0, 1.0)), }; if let Some(source) = source { graphics::draw( context, &self.image, DrawParam::default() .dest(self.destination) .src(source) .scale(self.scale), )?; } Ok(()) } pub fn update(&mut self) { match self.frame { CosmonautFrames::None => (), _ => { if self.timer.elapsed().as_millis() > 50 { self.frame.next(); self.timer = Instant::now(); } } } } pub fn start(&mut self) { if let CosmonautFrames::None = self.frame { if self.timer.elapsed().as_secs() > 5 { self.timer = Instant::now(); self.frame.next() } } } pub fn contains(&self, position: Point2) -> bool { position.x > self.destination.x && position.y > self.destination.y && position.x < self.destination.x + TILE_WIDTH * TILE_SCALE * 2.0 && position.y < self.destination.y + TILE_WIDTH * TILE_SCALE * 2.0 } } #[derive(Clone, Copy)] struct Cell { occupant: Occupant, position: Point2, hover: bool, clicked: bool, } impl Cell { pub fn new(position: Point2) -> Cell { Cell { occupant: rand::random(), position, hover: false, clicked: false, } } pub fn contains(&self, position: Point2) -> bool { position.x > self.position.x && position.y > self.position.y && position.x < self.position.x + TILE_WIDTH * TILE_SCALE && position.y < self.position.y + TILE_WIDTH * TILE_SCALE } pub fn moveable(&self) -> bool { match self.occupant { Occupant::Explosion { .. } => false, Occupant::None => false, _ => true, } } pub fn clicked_on(&mut self) { self.clicked = true; } pub fn clicked_off(&mut self) { self.clicked = false; } pub fn hover_on(&mut self) { self.hover = true; } pub fn hover_off(&mut self) { self.hover = false; } 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: TILE_SCALE, y: TILE_SCALE, }), ); } if self.hover { let mesh = Mesh::new_rectangle( context, DrawMode::Stroke(StrokeOptions::default()), Rect::new( self.position.x, self.position.y, TILE_WIDTH * TILE_SCALE, TILE_HEIGHT * TILE_SCALE, ), Color::from_rgb(255, 100, 100), )?; graphics::draw(context, &mesh, DrawParam::default())?; } if self.clicked { let mesh = Mesh::new_rectangle( context, DrawMode::Stroke(StrokeOptions::default()), Rect::new( self.position.x, self.position.y, TILE_WIDTH * TILE_SCALE, TILE_HEIGHT * TILE_SCALE, ), Color::from_rgb(100, 255, 100), )?; graphics::draw(context, &mesh, DrawParam::default())?; } Ok(()) } } struct Game { selected: Option<(usize, usize)>, spritebatch: SpriteBatch, grid: Vec>, background: Image, cosmonaut: Cosmonaut, } impl Game { fn new(context: &mut Context) -> GameResult { let mut background = Image::new(context, "/background.png")?; background.set_filter(FilterMode::Nearest); let mut image = Image::new(context, "/tileset.png")?; image.set_filter(FilterMode::Nearest); image.set_wrap(WrapMode::Mirror, WrapMode::Mirror); let mut grid = Vec::new(); let mut y = 0.0; for _ in 0..COLUMNS { let mut column = Vec::new(); for j in 0..ROWS { column.push(Cell::new(Point2 { x: (j as f32) * (TILE_WIDTH * TILE_SCALE + BORDER_SIZE) + SHIFT_X, y: y + SHIFT_Y, })); } y += TILE_HEIGHT * TILE_SCALE + BORDER_SIZE; grid.push(column); } Ok(Game { grid, selected: None, cosmonaut: Cosmonaut::new(context)?, spritebatch: SpriteBatch::new(image), background, }) } fn update_explosions(&mut self) { let mut last = Occupant::None; let mut connected = Vec::new(); for i in 0..COLUMNS { let mut c = Vec::new(); for j in 0..ROWS { if self.grid[i][j].occupant == last && last != Occupant::None { c.push((i, j)); c.push((i, j - 1)); } else { connected.push(c.clone()); c.clear(); } last = self.grid[i][j].occupant; } connected.push(c); last = Occupant::None; } for c in connected.iter() { if c.len() > 3 { for (i, j) in c.iter() { self.grid[*i][*j].occupant = Occupant::Explosion { frame: 0, timer: Instant::now(), }; } } } connected.clear(); for i in 0..COLUMNS { let mut c = Vec::new(); for j in 0..ROWS { if self.grid[j][i].occupant == last && last != Occupant::None { c.push((j, i)); c.push((j - 1, i)); } else { connected.push(c.clone()); c.clear(); } last = self.grid[j][i].occupant; } connected.push(c); last = Occupant::None; } for c in connected.iter() { if c.len() > 3 { for (j, i) in c.iter() { self.grid[*j][*i].occupant = Occupant::Explosion { frame: 0, timer: Instant::now(), }; } } } } fn update_dropping(&mut self) { for i in 1..COLUMNS { for j in 0..ROWS { if self.grid[i][j].occupant == Occupant::None && self.grid[i - 1][j].occupant != Occupant::None { self.grid[i][j].occupant = self.grid[i - 1][j].occupant; self.grid[i - 1][j].occupant = Occupant::None; } } } } fn update_feeding(&mut self) { for i in 0..COLUMNS { if self.grid[0][i].occupant == Occupant::None { self.grid[0][i].occupant = rand::random(); } } } fn update_frames(&mut self) { for row in self.grid.iter_mut() { for cell in row.iter_mut() { let mut done = false; if let Occupant::Explosion { ref mut frame, ref mut timer, } = cell.occupant { if timer.elapsed().as_millis() > 500 { if *frame < 4 { *frame += 1; *timer = Instant::now(); } else { done = true; } } } if done { cell.occupant = Occupant::None; } } } } } impl EventHandler for Game { fn update(&mut self, context: &mut Context) -> GameResult { for row in self.grid.iter_mut() { for cell in row.iter_mut() { cell.hover_off(); } } let position = mouse::position(context); if self.cosmonaut.contains(position) { self.cosmonaut.start(); } self.cosmonaut.update(); for row in self.grid.iter_mut() { for cell in row.iter_mut() { if cell.contains(position) { cell.hover_on(); } } } self.update_explosions(); self.update_frames(); self.update_dropping(); self.update_feeding(); Ok(()) } fn mouse_button_down_event( &mut self, _context: &mut Context, button: mouse::MouseButton, x: f32, y: f32, ) { if button == mouse::MouseButton::Left { let position = Point2 { x, y }; for (i, row) in self.grid.iter_mut().enumerate() { for (j, cell) in row.iter_mut().enumerate() { if cell.contains(position) && cell.moveable() { self.selected = Some((i, j)); cell.clicked_on(); } } } } } fn mouse_button_up_event( &mut self, _context: &mut Context, button: mouse::MouseButton, x: f32, y: f32, ) { if button == mouse::MouseButton::Left { for row in self.grid.iter_mut() { for cell in row.iter_mut() { cell.clicked_off(); } } if let Some(selected) = self.selected { let position = Point2 { x, y }; let mut swap = None; for (i, row) in self.grid.iter_mut().enumerate() { for (j, cell) in row.iter_mut().enumerate() { if cell.contains(position) && cell.moveable() && (((i + 1 == selected.0) && (j == selected.1)) || ((i.overflowing_sub(1).0 == selected.0) && (j == selected.1)) || ((i == selected.0) && (j + 1 == selected.1)) || ((i == selected.0) && (j.overflowing_sub(1)).0 == selected.1)) { swap = Some((i, j)); } } } if let Some((i, j)) = swap { let clone = self.grid[i][j].occupant; self.grid[i][j].occupant = self.grid[selected.0][selected.1].occupant; self.grid[selected.0][selected.1].occupant = clone; self.selected = None; } } } } fn draw(&mut self, context: &mut Context) -> GameResult { graphics::clear(context, [0.0, 0.0, 0.0, 1.0].into()); graphics::draw(context, &self.background, DrawParam::default())?; self.cosmonaut.draw(context)?; for row in self.grid.iter() { for cell in row.iter() { cell.draw(context, &mut self.spritebatch)?; } } graphics::draw(context, &self.spritebatch, DrawParam::default())?; self.spritebatch.clear(); graphics::present(context)?; Ok(()) } } 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) }