Проектная работа - WIP

This commit is contained in:
13 changed files with 149 additions and 225 deletions
+1 -4
View File
@@ -1,10 +1,7 @@
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(HelloPlugin)
.run();
App::new().add_plugins(DefaultPlugins).add_plugins(HelloPlugin).run();
}
#[derive(Component)]
+3 -13
View File
@@ -99,11 +99,7 @@ fn draw(
) {
if *i == 0 {
// Generate a random color on first run.
*draw_color = Color::linear_rgb(
seeded_rng.0.random(),
seeded_rng.0.random(),
seeded_rng.0.random(),
);
*draw_color = Color::linear_rgb(seeded_rng.0.random(), seeded_rng.0.random(), seeded_rng.0.random());
}
// Get the image from Bevy's asset storage.
@@ -126,17 +122,11 @@ fn draw(
// If the old color is our current color, change our drawing color.
let tolerance = 1.0 / 255.0;
if old_color.distance(&draw_color) <= tolerance {
*draw_color = Color::linear_rgb(
seeded_rng.0.random(),
seeded_rng.0.random(),
seeded_rng.0.random(),
);
*draw_color = Color::linear_rgb(seeded_rng.0.random(), seeded_rng.0.random(), seeded_rng.0.random());
}
// Set the new color, but keep old alpha value from image.
image
.set_color_at(x, y, draw_color.with_alpha(old_color.alpha()))
.unwrap();
image.set_color_at(x, y, draw_color.with_alpha(old_color.alpha())).unwrap();
*i += 1;
}
+6 -25
View File
@@ -14,10 +14,7 @@ fn main() {
// Observers are systems that run when an event is "triggered". This observer runs whenever
// `ExplodeMines` is triggered.
.add_observer(
|explode_mines: On<ExplodeMines>,
mines: Query<&Mine>,
index: Res<SpatialIndex>,
mut commands: Commands| {
|explode_mines: On<ExplodeMines>, mines: Query<&Mine>, index: Res<SpatialIndex>, mut commands: Commands| {
// Access resources
for entity in index.get_nearby(explode_mines.pos) {
// Run queries
@@ -47,10 +44,7 @@ struct Mine {
impl Mine {
fn random(rand: &mut ChaCha8Rng) -> Self {
Mine {
pos: Vec2::new(
(rand.random::<f32>() - 0.5) * 1200.0,
(rand.random::<f32>() - 0.5) * 600.0,
),
pos: Vec2::new((rand.random::<f32>() - 0.5) * 1200.0, (rand.random::<f32>() - 0.5) * 600.0),
size: 4.0 + rand.random::<f32>() * 16.0,
}
}
@@ -116,20 +110,14 @@ fn setup(mut commands: Commands) {
fn on_add_mine(add: On<Add, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
let mine = query.get(add.entity).unwrap();
let tile = (
(mine.pos.x / CELL_SIZE).floor() as i32,
(mine.pos.y / CELL_SIZE).floor() as i32,
);
let tile = ((mine.pos.x / CELL_SIZE).floor() as i32, (mine.pos.y / CELL_SIZE).floor() as i32);
index.map.entry(tile).or_default().insert(add.entity);
}
// Remove despawned mines from our index
fn on_remove_mine(remove: On<Remove, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
let mine = query.get(remove.entity).unwrap();
let tile = (
(mine.pos.x / CELL_SIZE).floor() as i32,
(mine.pos.y / CELL_SIZE).floor() as i32,
);
let tile = ((mine.pos.x / CELL_SIZE).floor() as i32, (mine.pos.y / CELL_SIZE).floor() as i32);
index.map.entry(tile).and_modify(|set| {
set.remove(&remove.entity);
});
@@ -153,11 +141,7 @@ fn explode_mine(explode: On<Explode>, query: Query<&Mine>, mut commands: Command
// Draw a circle for each mine using `Gizmos`
fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) {
for mine in &mines {
gizmos.circle_2d(
mine.pos,
mine.size,
Color::hsl((mine.size - 4.0) / 16.0 * 360.0, 1.0, 0.8),
);
gizmos.circle_2d(mine.pos, mine.size, Color::hsl((mine.size - 4.0) / 16.0 * 360.0, 1.0, 0.8));
}
}
@@ -194,10 +178,7 @@ const CELL_SIZE: f32 = 64.0;
impl SpatialIndex {
// Lookup all entities within adjacent cells of our spatial index
fn get_nearby(&self, pos: Vec2) -> Vec<Entity> {
let tile = (
(pos.x / CELL_SIZE).floor() as i32,
(pos.y / CELL_SIZE).floor() as i32,
);
let tile = ((pos.x / CELL_SIZE).floor() as i32, (pos.y / CELL_SIZE).floor() as i32);
let mut nearby = Vec::new();
for x in -1..2 {
for y in -1..2 {
+8 -34
View File
@@ -1,4 +1,3 @@
use bevy::math::bounding::{Aabb2d, BoundingCircle, BoundingVolume, IntersectsVolume};
use bevy::prelude::*;
@@ -7,10 +6,7 @@ fn main() {
.add_plugins(DefaultPlugins)
.insert_resource(ClearColor(Color::srgb(0.8, 0.8, 1.0)))
.add_systems(Startup, setup)
.add_systems(
FixedUpdate,
(move_paddle, apply_velocity, check_for_collisions),
)
.add_systems(FixedUpdate, (move_paddle, apply_velocity, check_for_collisions))
.run();
}
@@ -40,11 +36,7 @@ struct Ball;
#[derive(Component)]
struct Collider;
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<ColorMaterial>>) {
commands.spawn(Camera2d);
let paddle_y = BOTTOM_WALL + PADDLE_GAP_Y;
@@ -77,11 +69,7 @@ fn setup(
commands.spawn(Wall::new(WallLocation::Bottom));
}
fn move_paddle(
input: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
mut paddle_transform: Single<&mut Transform, With<Paddle>>,
) {
fn move_paddle(input: Res<ButtonInput<KeyCode>>, time: Res<Time>, mut paddle_transform: Single<&mut Transform, With<Paddle>>) {
let direction = if input.pressed(KeyCode::ArrowLeft) {
-1.0
} else if input.pressed(KeyCode::ArrowRight) {
@@ -107,19 +95,13 @@ fn apply_velocity(time: Res<Time>, mut query: Query<(&mut Transform, &Velocity)>
}
}
fn check_for_collisions(
ball_query: Single<(&mut Velocity, &Transform), With<Ball>>,
collider_query: Query<&Transform, With<Collider>>,
) {
fn check_for_collisions(ball_query: Single<(&mut Velocity, &Transform), With<Ball>>, collider_query: Query<&Transform, With<Collider>>) {
let (mut ball_velocity, ball_transform) = ball_query.into_inner();
for collider_transform in &collider_query {
let collision = ball_collision(
BoundingCircle::new(ball_transform.translation.truncate(), BALL_DIAMETER / 2.0),
Aabb2d::new(
collider_transform.translation.truncate(),
collider_transform.scale.truncate() / 2.0,
),
Aabb2d::new(collider_transform.translation.truncate(), collider_transform.scale.truncate() / 2.0),
);
if let Some(collision) = collision {
@@ -160,11 +142,7 @@ fn ball_collision(circle: BoundingCircle, rect: Aabb2d) -> Option<Collision> {
let offset = circle.center() - closest;
let side = if offset.x.abs() > offset.y.abs() {
if offset.x < 0.0 {
Collision::Left
} else {
Collision::Right
}
if offset.x < 0.0 { Collision::Left } else { Collision::Right }
} else if offset.y > 0.0 {
Collision::Top
} else {
@@ -212,12 +190,8 @@ impl WallLocation {
fn size(&self) -> Vec2 {
match self {
WallLocation::Left | WallLocation::Right => {
Vec2::new(WALL_WIDTH, TOP_WALL - BOTTOM_WALL)
}
WallLocation::Top | WallLocation::Bottom => {
Vec2::new(RIGHT_WALL - LEFT_WALL, WALL_WIDTH)
}
WallLocation::Left | WallLocation::Right => Vec2::new(WALL_WIDTH, TOP_WALL - BOTTOM_WALL),
WallLocation::Top | WallLocation::Bottom => Vec2::new(RIGHT_WALL - LEFT_WALL, WALL_WIDTH),
}
}
}
+3 -9
View File
@@ -47,21 +47,15 @@ fn runner(mut app: App) -> AppExit {
}
"f" => {
println!("FAST: setting relative speed to 2x");
app.world_mut()
.resource_mut::<Time<Virtual>>()
.set_relative_speed(2.0);
app.world_mut().resource_mut::<Time<Virtual>>().set_relative_speed(2.0);
}
"n" => {
println!("NORMAL: setting relative speed to 1x");
app.world_mut()
.resource_mut::<Time<Virtual>>()
.set_relative_speed(1.0);
app.world_mut().resource_mut::<Time<Virtual>>().set_relative_speed(1.0);
}
"s" => {
println!("SLOW: setting relative speed to 0.5x");
app.world_mut()
.resource_mut::<Time<Virtual>>()
.set_relative_speed(0.5);
app.world_mut().resource_mut::<Time<Virtual>>().set_relative_speed(0.5);
}
"p" => {
println!("PAUSE: pausing virtual clock");
+3
View File
@@ -0,0 +1,3 @@
max_width = 160
newline_style = "Unix"
fn_single_line = true
+2 -8
View File
@@ -8,11 +8,7 @@ pub fn print_all_entities(mut commands: Commands, query: Query<Entity>) {
}
/// ДЛЯ ОТЛАДКИ - вывод всех сущностей мира по нажатию Enter
pub fn print_all_entities_by_press_enter(
input: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
query: Query<Entity>,
) {
pub fn print_all_entities_by_press_enter(input: Res<ButtonInput<KeyCode>>, mut commands: Commands, query: Query<Entity>) {
if input.just_pressed(KeyCode::Enter) {
for entity in query {
commands.entity(entity).log_components();
@@ -28,9 +24,7 @@ pub struct Grid;
pub fn spawn_grid(commands: &mut Commands) {
use crate::domain::GRID_SIZE;
let grid = commands
.spawn((Grid, Transform::default(), Visibility::default()))
.id();
let grid = commands.spawn((Grid, Transform::default(), Visibility::default())).id();
for x in -20..=20 {
commands.spawn((
+32 -21
View File
@@ -1,7 +1,9 @@
use std::collections::HashMap;
use std::{collections::HashMap, fmt::Debug};
use bevy::prelude::*;
use crate::tools::Position;
pub const GRID_SIZE: u8 = 48;
pub const HEAD_SIZE: u8 = (GRID_SIZE as f32 * 1.1) as u8;
pub const SNAKE_VELOCITY: u8 = (GRID_SIZE as f32 / 1.0) as u8;
@@ -20,7 +22,19 @@ pub struct End;
/// Событие прохождения границы сетки
#[derive(EntityEvent)]
pub struct GridBarier(pub Entity);
pub struct GridBarier {
pub entity: Entity,
#[allow(unused)]
pub src_pos: Position,
#[allow(unused)]
pub dst_pos: Position,
}
impl GridBarier {
pub fn new(entity: Entity, src_pos: Position, dst_pos: Position) -> Self {
Self { entity, src_pos, dst_pos }
}
}
/// Направление движения
#[derive(Component, PartialEq, Clone, Copy, Debug)]
@@ -93,30 +107,27 @@ pub struct Inert(pub Direction);
/// Путь змеи
#[derive(Resource, Default, Debug)]
pub struct SnakePath(HashMap<(i16, i16), Direction>);
pub struct SnakePath(HashMap<Position, Direction>);
impl SnakePath {
fn to_key(transform: &Transform) -> (i16, i16) {
(
(transform.translation.x / GRID_SIZE as f32).floor() as i16,
(transform.translation.y / GRID_SIZE as f32).floor() as i16,
)
}
pub fn push(&mut self, transform: &Transform, direction: Direction) -> (i16, i16, Direction) {
let key = SnakePath::to_key(transform);
pub fn push(&mut self, transform: &Transform, direction: Direction) -> (Position, Direction) {
let key = transform.into();
self.0.insert(key, direction.clone());
(key.0, key.1, direction.clone())
(key, direction.clone())
}
pub fn pick(&self, transform: &Transform) -> Option<(i16, i16, Direction)> {
let key = SnakePath::to_key(transform);
self.0.get(&key).cloned().map(|d| (key.0, key.1, d))
pub fn pick(&self, transform: &Transform) -> Option<(Position, Direction)> {
let key = transform.into();
self.0.get(&key).cloned().map(|d| (key, d))
}
pub fn drop(&mut self, transform: &Transform) -> Option<(i16, i16, Direction)> {
let key = SnakePath::to_key(transform);
self.0.remove(&key).map(|d| (key.0, key.1, d))
pub fn drop(&mut self, transform: &Transform) -> Option<(Position, Direction)> {
let key = transform.into();
self.0.remove(&key).map(|d| (key, d))
}
pub fn debug(&self) -> impl Debug {
&self.0
}
}
@@ -132,10 +143,10 @@ mod test {
snake_path.push(&Transform::default(), Direction::default());
assert_eq!(snake_path.0.len(), 1);
assert!(snake_path.pick(&Transform::default()) == Some((0, 0, Direction::default())));
assert!(snake_path.pick(&Transform::default()) == Some((Position::default(), Direction::default())));
assert_eq!(snake_path.0.len(), 1);
assert!(snake_path.drop(&Transform::default()) == Some((0, 0, Direction::default())));
assert!(snake_path.drop(&Transform::default()) == Some((Position::default(), Direction::default())));
assert_eq!(snake_path.0.len(), 0);
}
}
+6 -16
View File
@@ -6,11 +6,7 @@ use crate::{
};
/// Система поворота головы змеи
pub fn head_rotation(
input: Res<ButtonInput<KeyCode>>,
query: Single<(&mut Transform, &mut Direction), With<Head>>,
mut snake_path: ResMut<SnakePath>,
) {
pub fn head_rotation(input: Res<ButtonInput<KeyCode>>, query: Single<(&mut Transform, &mut Direction), With<Head>>, mut snake_path: ResMut<SnakePath>) {
use core::f32::consts::PI;
let (mut transform, mut direction) = query.into_inner();
@@ -33,17 +29,14 @@ pub fn head_rotation(
}
/// Система движения для всех движущихся сущностей
pub fn moving(
time: Res<Time<Fixed>>,
query: Query<(&mut Transform, &mut Inert, &Direction, &Velocity, Entity)>,
mut commands: Commands,
) {
pub fn moving(time: Res<Time<Fixed>>, query: Query<(&mut Transform, &mut Inert, &Direction, &Velocity, Entity)>, mut commands: Commands) {
for (mut transform, mut inert, direction, Velocity(velocity), entity) in query.into_iter() {
let src_transftorm = transform.clone();
let delta = velocity * time.delta_secs();
let src = inert.0.get_coord(&transform);
let sign = inert.0.get_change_sign();
let dst = src + sign * delta;
let mut splited = _split_by_grid_tracing(src, dst, GRID_SIZE as f32).into_iter();
let mut splited = split_by_grid(src, dst, GRID_SIZE as f32).into_iter();
let first = splited.next().unwrap_or((0.0, 0.0));
inert.0.set_coord(&mut transform, first.0);
if let Some(second) = splited.next() {
@@ -53,16 +46,13 @@ pub fn moving(
let sign = inert.0.get_change_sign();
let dst = src + sign * delta;
inert.0.set_coord(&mut transform, dst);
commands.trigger(GridBarier(entity));
commands.trigger(GridBarier::new(entity, (&src_transftorm).into(), transform.into()));
}
}
}
fn _split_by_grid_tracing(src: f32, dst: f32, grid: f32) -> Vec<(f32, f32)> {
let result = split_by_grid(src, dst, grid);
trace!(
"\tsplit_by_grid({:?}, {:?}, {:?}) -> {:?}",
src, dst, grid, result
);
trace!("\tsplit_by_grid({:?}, {:?}, {:?}) -> {:?}", src, dst, grid, result);
result
}
+19 -30
View File
@@ -4,18 +4,12 @@ use bevy::prelude::*;
#[allow(unused)]
/// Слушатель событий [GridBarier] для хвоста, обновляющий направление его движения из ресурса [SnakePath] и позволяющий следовать за головой
pub fn tail_observer(
event: On<GridBarier>,
mut query: Query<(&mut Direction, &Transform)>,
snake_path: Res<SnakePath>,
) {
if event.observer() == event.0 {
if let Ok((mut direction, transform)) = query.get_mut(event.0) {
if let Some((x, y, new_direction)) = snake_path.pick(transform) {
trace!(
"change direction for {:?} in ({:?}, {:?}) to {:?}",
event.0, x, y, new_direction
);
pub fn tail_observer(event: On<GridBarier>, mut query: Query<(&mut Direction, &Transform)>, snake_path: Res<SnakePath>) {
let entity = event.entity;
if event.observer() == entity {
if let Ok((mut direction, transform)) = query.get_mut(event.entity) {
if let Some((position, new_direction)) = snake_path.pick(transform) {
trace!("change direction for {entity:?} in {position:?} to {new_direction:?}");
*direction = new_direction;
}
}
@@ -23,33 +17,28 @@ pub fn tail_observer(
}
/// Слушатель событий [GridBarier] для конца змеи, обновляющий направление его движения из ресурса [SnakePath] и удаляющий полученные значения из [SnakePath]
pub fn cleanup_snake_path(
event: On<GridBarier>,
mut commands: Commands,
query: Query<&Transform, With<End>>,
) {
if let Ok(transform) = query.get(event.0) {
pub fn cleanup_snake_path(event: On<GridBarier>, mut commands: Commands, query: Query<&Transform, With<End>>) {
if let Ok(transform) = query.get(event.entity) {
// trace!("schedule cleaning SnakePath for {:?}", transform);
let transform = transform.clone();
commands.queue(move |world: &mut World| {
let mut snake_path = world.resource_mut::<SnakePath>();
if let Some((x, y, dir)) = snake_path.drop(&transform) {
trace!(
"removed entry from SnakePath: ({:?}, {:?}) => {:?}\n\tsnake_path: {:?}",
x, y, dir, *snake_path
);
if let Some((pos, dir)) = snake_path.drop(&transform) {
let map = snake_path.debug();
trace!("removed entry from SnakePath: {pos:?} => {dir:?}\n\tsnake_path: {map:?}");
}
});
}
}
/// Запланировать установку скорости движения [Velocity] при следующем событии [GridBarier]
pub fn schedule_set_velocity(tail: Entity) -> impl Fn(On<GridBarier>, Commands) {
move |event: On<GridBarier>, mut commands: Commands| {
commands
.entity(tail)
.entry::<Velocity>()
.and_modify(|mut velocity| velocity.0 = SNAKE_VELOCITY as f32);
commands.entity(event.observer()).despawn();
pub fn schedule_set_velocity(tail: Entity) -> impl Fn(On<GridBarier>, Commands, Query<(&mut Velocity, &Transform)>) {
move |event: On<GridBarier>, mut commands: Commands, mut query: Query<(&mut Velocity, &Transform)>| {
if let Ok((mut velocity, transform)) = query.get_mut(tail) {
if event.dst_pos != transform.into() {
velocity.0 = SNAKE_VELOCITY as f32;
commands.entity(event.observer()).despawn();
}
}
}
}
+12 -17
View File
@@ -1,7 +1,7 @@
use bevy::prelude::*;
use crate::{
domain::{End, SnakePath, Tail},
domain::{End, GridBarier, SnakePath, Tail},
observers::{cleanup_snake_path, schedule_set_velocity, tail_observer},
};
@@ -20,16 +20,20 @@ fn spawn_snake(commands: &mut Commands) {
commands.insert_resource(SnakePath::default());
commands.spawn(Observer::new(cleanup_snake_path));
commands.spawn(Observer::new(|e: On<GridBarier>| {
let entity = e.entity;
let src = e.src_pos;
let dst = e.dst_pos;
trace!("...<{entity:?}> moving {src:>16} => {dst:>16}");
}));
let head = commands
.spawn((
Head,
Direction::default(),
Inert::default(),
Velocity(SNAKE_VELOCITY as f32),
Sprite::from_color(
Color::srgb(1., 1., 1.),
vec2(HEAD_SIZE as f32, HEAD_SIZE as f32),
),
Sprite::from_color(Color::srgb(1., 1., 1.), vec2(HEAD_SIZE as f32, HEAD_SIZE as f32)),
Transform {
translation: vec3(0.0, 0.0, 1.0),
..Default::default()
@@ -37,10 +41,7 @@ fn spawn_snake(commands: &mut Commands) {
))
.id();
commands.spawn((
Sprite::from_color(
Color::srgb(1., 0., 0.),
vec2(HEAD_SIZE as f32 * 0.3, HEAD_SIZE as f32 * 0.3),
),
Sprite::from_color(Color::srgb(1., 0., 0.), vec2(HEAD_SIZE as f32 * 0.3, HEAD_SIZE as f32 * 0.3)),
Transform {
translation: vec3(HEAD_SIZE as f32 * -0.25, HEAD_SIZE as f32 * 0.25, 1.0),
..Default::default()
@@ -48,10 +49,7 @@ fn spawn_snake(commands: &mut Commands) {
ChildOf(head),
));
commands.spawn((
Sprite::from_color(
Color::srgb(0., 0., 1.),
vec2(HEAD_SIZE as f32 * 0.2, HEAD_SIZE as f32 * 0.2),
),
Sprite::from_color(Color::srgb(0., 0., 1.), vec2(HEAD_SIZE as f32 * 0.2, HEAD_SIZE as f32 * 0.2)),
Transform {
translation: vec3(HEAD_SIZE as f32 * 0.25, HEAD_SIZE as f32 * 0.25, 1.0),
..Default::default()
@@ -66,10 +64,7 @@ fn spawn_snake(commands: &mut Commands) {
Direction::default(),
Inert::default(),
Velocity(0.0),
Sprite::from_color(
Color::srgb(0.7, 0.7, 0.7),
vec2(HEAD_SIZE as f32 * 0.8, HEAD_SIZE as f32 * 0.8),
),
Sprite::from_color(Color::srgb(0.7, 0.7, 0.7), vec2(HEAD_SIZE as f32 * 0.8, HEAD_SIZE as f32 * 0.8)),
Transform {
translation: vec3(0.0, 0.0, 0.0),
..Default::default()
+22 -48
View File
@@ -13,12 +13,7 @@ pub fn define_log_layer() -> Option<bevy::log::BoxedFmtLayer> {
.with_line_number(false)
.with_filter(targets.clone())
.boxed();
let file = match File::options()
.write(true)
.create(true)
.append(true)
.open("snake-game.log")
{
let file = match File::options().write(true).create(true).append(true).open("snake-game.log") {
Ok(file) => file,
Err(e) => {
eprintln!("No access to log file: {:?}", e);
@@ -54,9 +49,9 @@ pub fn split_by_grid(src: f32, dst: f32, grid: f32) -> Vec<(f32, f32)> {
let mut src: f32 = src;
for step in old_grid_right as i32..new_grid_right as i32 {
let step = step as f32 * grid;
if step != src {
output.push((step, step - src));
}
// if step != src {
output.push((step, step - src));
// }
src = step;
}
output.push((dst, dst - src));
@@ -64,9 +59,9 @@ pub fn split_by_grid(src: f32, dst: f32, grid: f32) -> Vec<(f32, f32)> {
let mut dst: f32 = dst;
for step in new_grid_right as i32..old_grid_right as i32 {
let step = step as f32 * grid;
if step != dst {
output.push((dst, dst - step));
}
// if step != dst {
output.push((dst, dst - step));
// }
dst = step;
}
output.push((dst, dst - src));
@@ -86,53 +81,32 @@ mod test {
assert_eq!(split_by_grid(3.0, 5.0, 5.0), vec![(5.0, 2.0)]);
assert_eq!(split_by_grid(3.0, 6.0, 5.0), vec![(5.0, 2.0), (6.0, 1.0)]);
assert_eq!(split_by_grid(3.0, 10.0, 5.0), vec![(5.0, 2.0), (10.0, 5.0)]);
assert_eq!(
split_by_grid(3.0, 11.0, 5.0),
vec![(5.0, 2.0), (10.0, 5.0), (11.0, 1.0)]
);
assert_eq!(split_by_grid(3.0, 11.0, 5.0), vec![(5.0, 2.0), (10.0, 5.0), (11.0, 1.0)]);
assert_eq!(split_by_grid(-2.0, 3.0, 5.0), vec![(0.0, 2.0), (3.0, 3.0)]);
assert_eq!(
split_by_grid(-6.0, 7.0, 5.0),
vec![(-5.0, 1.0), (0.0, 5.0), (5.0, 5.0), (7.0, 2.0)]
);
assert_eq!(
split_by_grid(-6.0, -2.0, 5.0),
vec![(-5.0, 1.0), (-2.0, 3.0)]
);
assert_eq!(split_by_grid(-6.0, 7.0, 5.0), vec![(-5.0, 1.0), (0.0, 5.0), (5.0, 5.0), (7.0, 2.0)]);
assert_eq!(split_by_grid(-6.0, -2.0, 5.0), vec![(-5.0, 1.0), (-2.0, 3.0)]);
// движение влево
assert_eq!(split_by_grid(4.0, 1.0, 5.0), vec![(1.0, -3.0)]);
assert_eq!(split_by_grid(5.0, 3.0, 5.0), vec![(3.0, -2.0)]);
assert_eq!(split_by_grid(6.0, 3.0, 5.0), vec![(5.0, -1.0), (3.0, -2.0)]);
assert_eq!(
split_by_grid(10.0, 3.0, 5.0),
vec![(5.0, -5.0), (3.0, -2.0)]
);
assert_eq!(
split_by_grid(11.0, 3.0, 5.0),
vec![(10.0, -1.0), (5.0, -5.0), (3.0, -2.0)]
);
assert_eq!(
split_by_grid(3.0, -2.0, 5.0),
vec![(0.0, -3.0), (-2.0, -2.0)]
);
assert_eq!(
split_by_grid(7.0, -6.0, 5.0),
vec![(5.0, -2.0), (0.0, -5.0), (-5.0, -5.0), (-6.0, -1.0)]
);
assert_eq!(
split_by_grid(-2.0, -6.0, 5.0),
vec![(-5.0, -3.0), (-6.0, -1.0)]
);
assert_eq!(split_by_grid(10.0, 3.0, 5.0), vec![(5.0, -5.0), (3.0, -2.0)]);
assert_eq!(split_by_grid(11.0, 3.0, 5.0), vec![(10.0, -1.0), (5.0, -5.0), (3.0, -2.0)]);
assert_eq!(split_by_grid(3.0, -2.0, 5.0), vec![(0.0, -3.0), (-2.0, -2.0)]);
assert_eq!(split_by_grid(7.0, -6.0, 5.0), vec![(5.0, -2.0), (0.0, -5.0), (-5.0, -5.0), (-6.0, -1.0)]);
assert_eq!(split_by_grid(-2.0, -6.0, 5.0), vec![(-5.0, -3.0), (-6.0, -1.0)]);
// сценарии с нулевой сеткой
assert_eq!(split_by_grid(1.0, 4.0, 0.0), vec![(4.0, 3.0)]);
assert_eq!(split_by_grid(4.0, 1.0, 0.0), vec![(1.0, -3.0)]);
// Сценарии со стартом в узле сетки
assert_eq!(split_by_grid(0.0, 1.5, 5.0), vec![(1.5, 1.5)]);
assert_eq!(split_by_grid(5.0, 7.0, 5.0), vec![(7.0, 2.0)]);
assert_eq!(split_by_grid(0.0, -1.5, 5.0), vec![(-1.5, -1.5)]);
assert_eq!(split_by_grid(5.0, 3.0, 5.0), vec![(3.0, -2.0)]);
assert_eq!(split_by_grid(0.0, 1.5, 5.0), vec![(0.0, 0.0), (1.5, 1.5)]);
assert_eq!(split_by_grid(5.0, 7.0, 5.0), vec![(5.0, 0.0), (7.0, 2.0)]);
assert_eq!(split_by_grid(0.0, -1.5, 5.0), vec![(0.0, 0.0), (-1.5, -1.5)]);
assert_eq!(split_by_grid(5.0, 3.0, 5.0), vec![(5.0, 0.0), (3.0, -2.0)]);
}
}
mod position;
pub use position::Position;
+32
View File
@@ -0,0 +1,32 @@
use std::{fmt::Display, ops::Deref};
use bevy::transform::components::Transform;
use crate::domain::GRID_SIZE;
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Position {
pub x: i8,
pub y: i8,
}
impl Position {
pub fn new(x: i8, y: i8) -> Self {
Self { x, y }
}
}
impl Display for Position {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(x:{}, y:{})", self.x, self.y)
}
}
impl<T: Deref<Target = Transform>> From<T> for Position {
fn from(value: T) -> Self {
Self {
x: (value.translation.x / GRID_SIZE as f32) as i8,
y: (value.translation.y / GRID_SIZE as f32) as i8,
}
}
}