summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Barrett <tom@tombarrett.xyz>2021-04-10 13:08:00 +0200
committerTom Barrett <tom@tombarrett.xyz>2021-04-10 13:08:00 +0200
commitd8a5e679e931c302da03d675caf4bd208dbde988 (patch)
treebd568a7f62403bdfdf3e3fbc41e434f9fa118b66 /src
parent07eb2dc0cbeaa7eb64645a86bd11842717a107a6 (diff)
parent893805baa14972dc6c1c28170856097bb54bf280 (diff)
Merge branch 'bevy_5'
moved to bevy
Diffstat (limited to 'src')
-rw-r--r--src/cell.rs229
-rw-r--r--src/constants.rs43
-rw-r--r--src/lib.rs2
-rw-r--r--src/main.rs307
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;
diff --git a/src/lib.rs b/src/lib.rs
index 78bbb9a..b7fde1e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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();
}