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, }; 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; #[derive(Clone, Copy, PartialEq)] enum Occupant { None, Green, Yellow, Diamond, Red, Explosion, } 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)] 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 { if 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 { return true; } false } 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 => None, Occupant::Green => Some(Rect::new(0.0, 0.0, 0.25, 1.0)), Occupant::Yellow => Some(Rect::new(0.25, 0.0, 0.25, 1.0)), Occupant::Diamond => Some(Rect::new(0.50, 0.0, 0.25, 1.0)), Occupant::Red => Some(Rect::new(0.75, 0.0, 0.25, 1.0)), }; 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, } 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, "/gem.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..8 { let mut column = Vec::new(); column.push(Cell::new(Point2 { x: SHIFT_X, y: y + SHIFT_Y, })); column.push(Cell::new(Point2 { x: 1.0 * (TILE_WIDTH * TILE_SCALE + BORDER_SIZE) + SHIFT_X, y: y + SHIFT_Y, })); column.push(Cell::new(Point2 { x: 2.0 * (TILE_WIDTH * TILE_SCALE + BORDER_SIZE) + SHIFT_X, y: y + SHIFT_Y, })); column.push(Cell::new(Point2 { x: 3.0 * (TILE_WIDTH * TILE_SCALE + BORDER_SIZE) + SHIFT_X, y: y + SHIFT_Y, })); column.push(Cell::new(Point2 { x: 4.0 * (TILE_WIDTH * TILE_SCALE + BORDER_SIZE) + SHIFT_X, y: y + SHIFT_Y, })); column.push(Cell::new(Point2 { x: 5.0 * (TILE_WIDTH * TILE_SCALE + BORDER_SIZE) + SHIFT_X, y: y + SHIFT_Y, })); column.push(Cell::new(Point2 { x: 6.0 * (TILE_WIDTH * TILE_SCALE + BORDER_SIZE) + SHIFT_X, y: y + SHIFT_Y, })); column.push(Cell::new(Point2 { x: 7.0 * (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, spritebatch: SpriteBatch::new(image), background, }) } } 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); for row in self.grid.iter_mut() { for cell in row.iter_mut() { if cell.contains(position) { cell.hover_on(); } } } let mut last = Occupant::None; let mut connected = Vec::new(); for (i, row) in self.grid.iter_mut().enumerate() { let mut c = Vec::new(); for (j, cell) in row.iter_mut().enumerate() { if cell.occupant == last { c.push((i, j)); c.push((i, j - 1)); } else { connected.push(c.clone()); c.clear(); } last = cell.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; } } } 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) { 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) && (((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())?; 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) }