Compare commits

..

No commits in common. '90a6181e2282900d91712c0c4453933ab16ec945' and '4c35daac3e9663c6dd2a11bcd7137276c051971c' have entirely different histories.

@ -15,7 +15,6 @@ path = "examples/demos/parallax3d.rs"
hide_debug = [] hide_debug = []
[dependencies] [dependencies]
itertools = "*"
thiserror = "2.0.12" thiserror = "2.0.12"
[dependencies.serde] [dependencies.serde]

@ -1,15 +1,16 @@
// Bevy basically forces "complex types" with Querys // Bevy basically forces "complex types" with Querys
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use itertools::Itertools;
use games::*; use games::*;
// *TODO: Detect when piece is going to go out of bounds and restirct parent from moving there
// TODO: When shape touches the rest of the pieces, re-parent to line entity
// TODO: When line is "full" (has 10 children) clear line and add to score // TODO: When line is "full" (has 10 children) clear line and add to score
fn main() { fn main() {
App::new() App::new()
.add_plugins(BaseGamePlugin { .add_plugins(BaseGamePlugin {
name: "falling-block-adventure".into(), name: "falling-block-rpg".into(),
target_resolution: (640.0, 480.0).into(), target_resolution: (640.0, 480.0).into(),
game_type: GameType::Two, game_type: GameType::Two,
..default() ..default()
@ -23,14 +24,18 @@ fn main() {
falling falling
.run_if(in_state(Falling::On)) .run_if(in_state(Falling::On))
.run_if(clock_cycle(1.0)), .run_if(clock_cycle(1.0)),
update_shape_blocks set_shape.run_if(
.run_if(any_component_added::<Shape>.or(any_component_changed::<Shape>)), any_component_added::<Shape>
.or(any_component_changed::<Shape>)
.or(any_component_added::<Orientation>)
.or(any_component_changed::<Orientation>),
),
update_relative_position,
update_position.after(update_relative_position),
sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>), sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>),
sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>), sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>),
update_position,
add_piece.run_if(not(any_with_component::<Shape>)), add_piece.run_if(not(any_with_component::<Shape>)),
clear_line.run_if(any_component_changed::<LineBlocks>), clear_line.run_if(any_component_changed::<LineBlocks>),
adjust_block_lines.run_if(any_component_changed::<Line>),
), ),
) )
.add_systems(Update, draw_grid) .add_systems(Update, draw_grid)
@ -58,7 +63,7 @@ struct ShapeBlock {
} }
// The blocks making up this shape // The blocks making up this shape
#[derive(Component, Default, Debug)] #[derive(Component, Default)]
#[relationship_target(relationship = LineBlock)] #[relationship_target(relationship = LineBlock)]
struct LineBlocks(Vec<Entity>); struct LineBlocks(Vec<Entity>);
@ -72,13 +77,44 @@ struct LineBlock {
} }
// A line holds up to 10 blocks before being cleared // A line holds up to 10 blocks before being cleared
#[derive(Component, Debug, Clone, Copy)] #[derive(Component, Debug)]
struct Line(usize); struct Line(u8);
// Just marks a block either of a shape or line // Just marks a block either of a shape or line
#[derive(Component, Debug)] #[derive(Component, Debug)]
struct Block; struct Block;
#[derive(Component, Event, Debug, Clone, Copy, PartialEq)]
#[require(GridPosition)]
struct RelativePosition {
x: i8,
y: i8,
}
impl RelativePosition {
fn up() -> Self {
RelativePosition { x: 0, y: 1 }
}
fn down() -> Self {
RelativePosition { x: 0, y: -1 }
}
fn left() -> Self {
RelativePosition { x: -1, y: 0 }
}
fn right() -> Self {
RelativePosition { x: 1, y: 0 }
}
}
impl From<(i8, i8)> for RelativePosition {
fn from((x, y): (i8, i8)) -> RelativePosition {
RelativePosition { x, y }
}
}
#[derive(Component, Debug, Clone, Copy, PartialEq)] #[derive(Component, Debug, Clone, Copy, PartialEq)]
#[require(Transform, Visibility)] #[require(Transform, Visibility)]
struct GridPosition { struct GridPosition {
@ -87,28 +123,65 @@ struct GridPosition {
} }
impl GridPosition { impl GridPosition {
fn with_offset(self, other_x: isize, other_y: isize) -> Result<GridPosition, GameError> { fn move_up(&self) -> Self {
let x = self.x as isize + other_x; Self {
let y = self.y as isize + other_y; y: if self.y + 1 < Y_MAX {
self.y.saturating_add(1)
} else {
self.y
},
x: self.x,
}
}
if x >= X_MAX as isize { fn move_down(&mut self) -> Self {
Err(GameError::OutOfBoundsLeft) Self {
} else if x < 0 { y: self.y.saturating_sub(1),
Err(GameError::OutOfBoundsRight) x: self.x,
} else if y < 0 { }
Err(GameError::OutOfBoundsDown) }
fn move_left(&mut self) -> Self {
Self {
x: self.x.saturating_sub(1),
y: self.y,
}
}
fn move_right(&mut self) -> Self {
Self {
x: if self.x + 1 < X_MAX {
self.x.saturating_add(1)
} else { } else {
Ok(GridPosition { self.x
x: x as u32, },
y: y as u32, y: self.y,
})
} }
} }
}
impl Display for GridPosition { fn is_colliding_with(&self, other: &Self) -> bool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.x == other.x && self.y.saturating_sub(1) == other.y
write!(f, "({},{})", self.x, self.y) }
fn add_relative(
&self,
RelativePosition { x: x1, y: y1 }: &RelativePosition,
) -> Result<Self, GameError> {
let x = self
.x
.checked_add_signed(*x1 as i32)
.ok_or(GameError::OutOfBoundsLeft)?;
let y = self
.y
.checked_add_signed(*y1 as i32)
.ok_or(GameError::OutOfBoundsDown)?;
if x >= X_MAX {
// TODO: y > Y_MAX?
Err(GameError::OutOfBoundsRight)
} else {
debug!("Moving to {x},{y}");
Ok(GridPosition { x, y })
}
} }
} }
@ -135,6 +208,7 @@ impl From<&GridPosition> for Vec3 {
// Grid Positions start in the bottom left of the area // Grid Positions start in the bottom left of the area
// So (0, 0) is the bottom left, (0, 9) is the bottom right, etc // So (0, 0) is the bottom left, (0, 9) is the bottom right, etc
// TODO: Custom offset allowing pieces like O and I to have correct center
let x_0 = -SCALE * 5.0 + (0.5 * SCALE); let x_0 = -SCALE * 5.0 + (0.5 * SCALE);
let x = x_0 + ((*x as f32) * SCALE); let x = x_0 + ((*x as f32) * SCALE);
@ -262,7 +336,7 @@ fn init_debug_ui(mut commands: Commands) {
}); });
} }
#[derive(Component, Debug, Clone, Copy)] #[derive(Component, Debug)]
enum Shape { enum Shape {
M4(Mat4), M4(Mat4),
M3(Mat3), M3(Mat3),
@ -291,63 +365,63 @@ impl Shape {
fn new_o() -> Self { fn new_o() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ Self::from_mat4(Mat4::from_cols_array_2d(&[
[0., 0., 0., 0.], [0.,0.,0.,0.],
[0., 1., 1., 0.], [0.,1.,1.,0.],
[0., 1., 1., 0.], [0.,1.,1.,0.],
[0., 0., 0., 0.], [0.,0.,0.,0.],
])) ]))
} }
fn new_t() -> Self { fn new_t() -> Self {
Self::from_mat3(Mat3::from_cols_array_2d(&[ Self::from_mat3(Mat3::from_cols_array_2d(&[
[0., 1., 0.], [0.,1.,0.],
[1., 1., 1.], [1.,1.,1.],
[0., 0., 0.], [0.,0.,0.],
])) ]))
} }
fn new_l() -> Self { fn new_l() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ Self::from_mat4(Mat4::from_cols_array_2d(&[
[0., 0., 0., 0.], [0.,0.,0.,0.],
[0., 1., 0., 0.], [0.,1.,0.,0.],
[0., 1., 0., 0.], [0.,1.,0.,0.],
[0., 1., 1., 0.], [0.,1.,1.,0.],
])) ]))
} }
fn new_j() -> Self { fn new_j() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ Self::from_mat4(Mat4::from_cols_array_2d(&[
[0., 0., 0., 0.], [0.,0.,0.,0.],
[0., 0., 1., 0.], [0.,0.,1.,0.],
[0., 0., 1., 0.], [0.,0.,1.,0.],
[0., 1., 1., 0.], [0.,1.,1.,0.],
])) ]))
} }
fn new_s() -> Self { fn new_s() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ Self::from_mat4(Mat4::from_cols_array_2d(&[
[0., 0., 0., 0.], [0.,0.,0.,0.],
[0., 1., 1., 0.], [0.,1.,1.,0.],
[1., 1., 0., 0.], [1.,1.,0.,0.],
[0., 0., 0., 0.], [0.,0.,0.,0.],
])) ]))
} }
fn new_z() -> Self { fn new_z() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ Self::from_mat4(Mat4::from_cols_array_2d(&[
[0., 0., 0., 0.], [0.,0.,0.,0.],
[1., 1., 0., 0.], [1.,1.,0.,0.],
[0., 1., 1., 0.], [0.,1.,1.,0.],
[0., 0., 0., 0.], [0.,0.,0.,0.],
])) ]))
} }
fn new_i() -> Self { fn new_i() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ Self::from_mat4(Mat4::from_cols_array_2d(&[
[0., 0., 1., 0.], [0.,0.,1.,0.],
[0., 0., 1., 0.], [0.,0.,1.,0.],
[0., 0., 1., 0.], [0.,0.,1.,0.],
[0., 0., 1., 0.], [0.,0.,1.,0.],
])) ]))
} }
@ -378,18 +452,16 @@ impl Shape {
*self = self.rotated(); *self = self.rotated();
} }
fn coordinates( // TODO: return impl Iterator<Item = &RelativePosition>
&self, fn relative_coordinates(&self) -> impl Iterator<Item = RelativePosition> {
center: &GridPosition, let mut v: Vec<RelativePosition> = Vec::new();
) -> impl Iterator<Item = Result<GridPosition, GameError>> {
let mut v: Vec<Result<GridPosition, GameError>> = Vec::new();
match self { match self {
Self::M4(inner) => { Self::M4(inner) => {
for (i, y) in (-1..3).rev().enumerate() { for (i, y) in (-1..3).rev().enumerate() {
let c = inner.col(i); let c = inner.col(i);
for (j, x) in (-1..3).enumerate() { for (j, x) in (-1..3).enumerate() {
if c[j] == 1.0 { if c[j] == 1.0 {
v.push(center.with_offset(x, y)); v.push(RelativePosition { x, y });
} }
} }
} }
@ -399,7 +471,7 @@ impl Shape {
let c = inner.col(i); let c = inner.col(i);
for (j, x) in (-1..2).enumerate() { for (j, x) in (-1..2).enumerate() {
if c[j] == 1.0 { if c[j] == 1.0 {
v.push(center.with_offset(x, y)); v.push(RelativePosition { x, y });
} }
} }
} }
@ -408,6 +480,10 @@ impl Shape {
v.into_iter() v.into_iter()
} }
fn computed_coordinates(&self, center: &GridPosition) -> impl Iterator<Item = Result<GridPosition, GameError>> {
self.relative_coordinates().map(|rp| center.add_relative(&rp))
}
fn as_ascii(&self) -> String { fn as_ascii(&self) -> String {
let mut output = String::default(); let mut output = String::default();
@ -417,67 +493,200 @@ impl Shape {
let col = this.col(i).to_array(); let col = this.col(i).to_array();
output += format!("{}{}{}{}\n", col[0], col[1], col[2], col[3]).as_str(); output += format!("{}{}{}{}\n", col[0], col[1], col[2], col[3]).as_str();
} }
} },
Self::M3(this) => { Self::M3(this) => {
for i in 0..3 { for i in 0..3 {
let col = this.col(i).to_array(); let col = this.col(i).to_array();
output += format!("{}{}{}\n", col[0], col[1], col[2]).as_str(); output += format!("{}{}{}\n", col[0], col[1], col[2]).as_str();
} }
} },
}; };
output output
} }
} }
fn update_position( #[cfg(test)]
mut changed: Query< mod test {
(Entity, &GridPosition, &mut Transform), use super::*;
Or<(Added<GridPosition>, Changed<GridPosition>)>,
>,
) {
changed.iter_mut().for_each(|(e, gp, mut t)| {
let v3: Vec3 = gp.into();
debug!(
"Updating {e} with grid position {:?} to coordinates {:?}",
gp, v3
);
t.translation = gp.into(); #[test]
}); fn test_shape_t() {
let mut shape = Shape::new_t();
let expected_up = "010\n\
111\n\
000\n";
let expected_right = "010\n\
011\n\
010\n";
let expected_down = "000\n\
111\n\
010\n";
let expected_left = "010\n\
110\n\
010\n";
assert_eq!(shape.as_ascii(), expected_up);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_right);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_down);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_left);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_up);
}
#[test]
fn test_shape_i() {
let mut shape = Shape::new_i();
let expected_up = "0010\n\
0010\n\
0010\n\
0010\n";
let expected_right = "0000\n\
0000\n\
1111\n\
0000\n";
let expected_down = "0100\n\
0100\n\
0100\n\
0100\n";
let expected_left = "0000\n\
1111\n\
0000\n\
0000\n";
assert_eq!(shape.as_ascii(), expected_up);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_right);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_down);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_left);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_up);
}
#[test]
fn test_relative_coordinates() {
let shape = Shape::new_t();
let expected: Vec<RelativePosition> = vec![
(0, 1).into(),
(-1, 0).into(),
(0, 0).into(),
(1, 0).into(),
];
let actual: Vec<RelativePosition> = shape.relative_coordinates().collect();
assert_eq!(actual, expected);
}
#[test]
fn test_computed_coordinates() {
let shape = Shape::new_t();
let center = GridPosition { x: 5, y: 5 };
let expected: Vec<Result<GridPosition, GameError>> = vec![
Ok((5, 6).into()),
Ok((4, 5).into()),
Ok((5, 5).into()),
Ok((6, 5).into()),
];
let actual: Vec<Result<GridPosition, GameError>> = shape.computed_coordinates(&center).collect();
assert_eq!(actual, expected);
}
} }
// TODO: Inline this to when movement occurs fn set_shape(
fn update_shape_blocks( query: Query<
query: Query<(Entity, &Shape, &Orientation, &GridPosition), Or<(Added<Shape>, Changed<Shape>)>>, (Entity, &Shape, &Orientation),
mut blocks: Query<&mut GridPosition, (With<ShapeBlock>, Without<Shape>)>, Or<(Added<Shape>, Changed<Shape>)>,
>,
mut blocks: Query<&mut RelativePosition, With<ShapeBlock>>,
mut commands: Commands, mut commands: Commands,
visuals: Res<Visuals>, visuals: Res<Visuals>,
) { ) {
query.iter().for_each(|(e, s, o, center)| { query.iter().for_each(|(e, s, o)| {
info!("Setting piece: {e:?} {o:?} {center:?}\n{}", s.as_ascii()); debug!("Setting piece: {e:?} {s:?} {o:?}");
let mesh = visuals.mesh.clone();
let mat = visuals.material.clone();
let positions: [RelativePosition;4] = todo!();
// todo: map positions to coordinates
if blocks.is_empty() { if blocks.is_empty() {
let mesh = Mesh2d(visuals.mesh.clone());
let mat = MeshMaterial2d(visuals.material.clone());
commands commands
.entity(e) .entity(e)
.with_related_entities::<ShapeBlock>(|parent| { .with_related_entities::<ShapeBlock>(|parent| {
s.coordinates(center).for_each(|gp| { positions.into_iter().for_each(|rp| {
parent parent
.spawn((mesh.clone(), mat.clone(), gp.unwrap(), Block)) .spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp, Block))
.observe(movement); .observe(movement);
}); });
}); });
} else { } else {
let mut p = s.coordinates(center); let mut p = positions.into_iter();
blocks.iter_mut().for_each(|mut gp| { blocks.iter_mut().for_each(|mut rp| {
*gp = p.next().unwrap().unwrap(); *rp = p.next().unwrap();
}); });
} }
}); });
} }
fn update_position(
mut changed: Query<
(Entity, &GridPosition, &mut Transform),
Or<(Added<GridPosition>, Changed<GridPosition>)>,
>,
) {
changed.iter_mut().for_each(|(e, gp, mut t)| {
let v3: Vec3 = gp.into();
debug!(
"Updating {e} with grid position {:?} to coordinates {:?}",
gp, v3
);
debug_assert!(gp.x < X_MAX, "block x > x_max");
t.translation = gp.into();
});
}
fn update_relative_position(
shape: Single<&GridPosition, With<ShapeBlocks>>,
mut query: Query<
(Entity, &mut GridPosition, &RelativePosition),
(
Without<ShapeBlocks>,
Or<(Added<RelativePosition>, Changed<RelativePosition>)>,
),
>,
) {
query.iter_mut().for_each(|(e, mut gp, rp)| {
debug!(
"Updating {e} grid position to {:?} + {:?} = {:?}",
gp,
rp,
gp.add_relative(rp).unwrap()
);
*gp = (*shape).add_relative(rp).unwrap();
});
}
fn kb_input( fn kb_input(
mut events: EventReader<KeyboardInput>, mut events: EventReader<KeyboardInput>,
mut query: Query<(Entity, &Orientation, &mut Shape)>, mut query: Query<(Entity, &Orientation, &mut Shape)>,
@ -490,21 +699,23 @@ fn kb_input(
key_code, state, .. key_code, state, ..
}| { }| {
if let ButtonState::Pressed = state { if let ButtonState::Pressed = state {
// TODO: Restict movement based on size/orientation of piece
// Check if children would be outside play area...
query.iter_mut().for_each(|(e, o, mut s)| { query.iter_mut().for_each(|(e, o, mut s)| {
match key_code { match key_code {
// Up arrow should rotate if in falling mode // Up arrow should rotate if in falling mode
// Only move up if in falling::off mode // Only move up if in falling::off mode
KeyCode::ArrowUp => { KeyCode::ArrowUp => {
commands.entity(e).trigger(Movement::Rotate); commands.entity(e).trigger(o.prev());
} }
KeyCode::ArrowDown => { KeyCode::ArrowDown => {
commands.entity(e).trigger(Movement::Down); commands.entity(e).trigger(RelativePosition::down());
} }
KeyCode::ArrowLeft => { KeyCode::ArrowLeft => {
commands.entity(e).trigger(Movement::Left); commands.entity(e).trigger(RelativePosition::left());
} }
KeyCode::ArrowRight => { KeyCode::ArrowRight => {
commands.entity(e).trigger(Movement::Right); commands.entity(e).trigger(RelativePosition::right());
} }
KeyCode::Space => next.set(match curr.get() { KeyCode::Space => next.set(match curr.get() {
Falling::On => Falling::Off, Falling::On => Falling::Off,
@ -539,7 +750,7 @@ fn draw_grid(mut gizmos: Gizmos) {
fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) { fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) {
shape.iter_mut().for_each(|e| { shape.iter_mut().for_each(|e| {
info!("Making {:?} fall", e); info!("Making {:?} fall", e);
commands.entity(e).trigger(Movement::Down); commands.entity(e).trigger(RelativePosition::down());
}); });
} }
@ -560,250 +771,102 @@ fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
fn add_piece(mut commands: Commands) { fn add_piece(mut commands: Commands) {
// TODO: Choose a different piece // TODO: Choose a different piece
commands commands
.spawn(( .spawn((Orientation::default(), GridPosition::default(), Shape::default()))
Orientation::default(), .observe(movement)
GridPosition::default(), .observe(rotation);
Shape::default(),
))
.observe(movement);
} }
/// When a line reaches 10 blocks, clear it /// When a line reaches 10 blocks, clear it
fn clear_line( fn clear_line(
changed_lines: Query<Entity, Changed<LineBlocks>>, lines: Query<(Entity, &LineBlocks), (With<LineBlocks>, Changed<LineBlocks>)>,
mut lines: Query<(Entity, &LineBlocks, &mut Line)>,
mut commands: Commands, mut commands: Commands,
) { ) {
let cleared_lines: Vec<usize> = changed_lines lines.iter().for_each(|(e, lb)| {
.iter()
.filter_map(|e| lines.get(e).ok())
.filter_map(|(e, lb, Line(i))| {
if lb.0.len() == 10 { if lb.0.len() == 10 {
commands.entity(e).despawn_related::<LineBlocks>(); commands.entity(e).despawn_related::<LineBlocks>();
Some(*i) // TODO: re-parent all blocks above this to the next line down
} else { // TODO: Parent blocks to lines for movement
None
}
})
.collect();
info!("Cleared lines: {:?}", cleared_lines);
for (idx, cleared_line_number) in cleared_lines.into_iter().sorted().enumerate() {
info!("Processing line {cleared_line_number} ({idx})");
let cleared_line_number = cleared_line_number - idx;
lines.iter_mut().for_each(|(_, _, mut l)| {
let dest = if l.0 > cleared_line_number {
l.0 - 1
} else if l.0 == cleared_line_number {
(Y_MAX - (idx + 1) as u32) as usize
} else {
l.0
};
info!("Moving line {:?} to {:?}", l, dest);
l.0 = dest;
});
}
}
fn adjust_block_lines(
query: Query<(Entity, &Line), Changed<Line>>,
parent: Query<&LineBlocks>,
mut blocks: Query<&mut GridPosition>,
) {
query.iter().for_each(|(e, Line(i))| {
parent.iter_descendants(e).for_each(|block| {
if let Ok(mut gp) = blocks.get_mut(block) {
gp.y = *i as u32;
} }
}); });
});
} }
#[derive(Event, Copy, Clone, PartialEq)]
enum Movement {
Down,
Left,
Right,
Rotate,
}
// TODO: When out of bounds left/right, try to move piece away from wall
fn movement( fn movement(
trigger: Trigger<Movement>, trigger: Trigger<RelativePosition>,
mut grid_positions: Query<&mut GridPosition, Or<(With<ShapeBlock>, With<ShapeBlocks>)>>, mut grid_positions: Query<&mut GridPosition, Or<(With<RelativePosition>, With<Shape>)>>,
mut shape: Query<&mut Shape>, parent: Query<&ShapeBlocks>,
inactive: Query<&GridPosition, (Without<ShapeBlock>, Without<ShapeBlocks>)>, inactive: Query<
&GridPosition,
(
Without<RelativePosition>,
Without<Shape>,
),
>,
mut commands: Commands, mut commands: Commands,
) { ) {
if let (Ok(this_shape), Ok(center)) = ( // Do a bunch of checks if this move is valid
shape.get_mut(trigger.target()), for block in parent.iter_descendants(trigger.target()) {
grid_positions.get(trigger.target()), let block_gp = grid_positions.get(block).unwrap();
) {
let (new_center, new_shape) = match trigger.event() { match block_gp.add_relative(trigger.event()) {
Movement::Down => (center.with_offset(0, -1), *this_shape),
Movement::Left => (center.with_offset(-1, 0), *this_shape),
Movement::Right => (center.with_offset(1, 0), *this_shape),
Movement::Rotate => (Ok(*center), this_shape.rotated()),
};
info!(
"Proposed change: {:?}\n{}",
new_center,
new_shape.as_ascii()
);
match new_center {
Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => (), // Do nothing
Err(GameError::OutOfBoundsDown) => {
commands.entity(trigger.target()).remove::<Shape>();
}
Err(GameError::Collision) => panic!("This shouldn't happen!"),
Ok(new_center) => {
let new_blocks = new_shape.coordinates(&new_center);
for block_gp in new_blocks {
match block_gp {
Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => { Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => {
return; // Hit the left/right wall, just ignore this move
} // Do nothing
Err(GameError::OutOfBoundsDown) => {
commands.entity(trigger.target()).remove::<Shape>();
return; return;
} }
Err(GameError::Collision) => panic!("This shouldn't happen!"), Err(GameError::OutOfBoundsDown) => {
Ok(gp) => {
for other_gp in inactive.iter() {
// If there would be a collision between blocks
if gp == *other_gp {
// And we are moving down
if *trigger.event() == Movement::Down {
// De-activate this piece
commands.entity(trigger.target()).remove::<Shape>(); commands.entity(trigger.target()).remove::<Shape>();
}
// Regardless, cancel the move
return; return;
} }
Err(GameError::Collision) => {
// Do nothing. add_relative does not return this variant
} }
Ok(new_block_gp) => {
let collision = inactive
.iter()
.any(|inactive_block_gp| new_block_gp == *inactive_block_gp);
if collision {
commands.entity(trigger.target()).remove::<Shape>();
} }
} }
} }
// Update center
let mut gp = grid_positions.get_mut(trigger.target()).unwrap();
*gp = new_center;
// Update shape/rotation
let mut s = shape.get_mut(trigger.target()).unwrap();
*s = new_shape;
}
} }
} else {
warn!("Triggered movement on non-shape entity"); // Move shape itself
if let Ok(mut shape_gp) = grid_positions.get_mut(trigger.target()) {
*shape_gp = shape_gp.add_relative(trigger.event()).unwrap();
} }
// Move the blocks of this shape
parent.iter_descendants(trigger.target()).for_each(|block| {
info!("Propogating movement {:?} to child {:?}", trigger.event(), block);
commands.entity(block).trigger(*trigger.event());
});
}
fn rotation(trigger: Trigger<Orientation>, mut q: Query<&mut Orientation>) {
let mut o = q.get_mut(trigger.target()).unwrap();
// If children would go out of bounds going left, move slightly to the right
// If that would cause collision, deactive piece
// If children would go out of bounds going right, move slightly to the left
// If that would cause collision, deactive piece
// If children would not go out of bounds going down
*o = trigger.event().clone();
} }
// TODO: Just despawn? // TODO: Just despawn?
fn deactive_shape( fn deactive_shape(
trigger: Trigger<OnRemove, Shape>, trigger: Trigger<OnRemove, Shape>,
grid_positions: Query<&GridPosition>,
parent: Query<&ShapeBlocks>, parent: Query<&ShapeBlocks>,
lines: Query<(Entity, &Line), With<LineBlocks>>,
mut commands: Commands, mut commands: Commands,
) { ) {
let v: Vec<Entity> = parent.iter_descendants(trigger.target()).collect();
parent.iter_descendants(trigger.target()).for_each(|block| { parent.iter_descendants(trigger.target()).for_each(|block| {
let GridPosition { y, .. } = grid_positions.get(block).unwrap(); commands.entity(block).remove::<RelativePosition>();
let parent_line = lines
.iter()
.find_map(|(e, Line(i))| (*y == *i as u32).then_some(e))
.unwrap();
commands
.entity(parent_line)
.add_one_related::<LineBlock>(block);
}); });
commands.entity(trigger.target()).despawn(); commands
.entity(trigger.target())
.remove_related::<ShapeBlock>(v.as_slice())
.despawn();
} }
#[cfg(test)] // TODO: When Orientation changed, perform matrix multiplication or something(?)
mod test {
use super::*;
#[test]
fn test_shape_t() {
let mut shape = Shape::new_t();
let expected_up = "010\n\
111\n\
000\n";
let expected_right = "010\n\
011\n\
010\n";
let expected_down = "000\n\
111\n\
010\n";
let expected_left = "010\n\
110\n\
010\n";
assert_eq!(shape.as_ascii(), expected_up);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_right);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_down);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_left);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_up);
}
#[test]
fn test_shape_i() {
let mut shape = Shape::new_i();
let expected_up = "0010\n\
0010\n\
0010\n\
0010\n";
let expected_right = "0000\n\
0000\n\
1111\n\
0000\n";
let expected_down = "0100\n\
0100\n\
0100\n\
0100\n";
let expected_left = "0000\n\
1111\n\
0000\n\
0000\n";
assert_eq!(shape.as_ascii(), expected_up);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_right);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_down);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_left);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_up);
}
#[test]
fn test_coordinates() {
let shape = Shape::new_t();
let center = GridPosition { x: 5, y: 5 };
let expected: Vec<Result<GridPosition, GameError>> = vec![
Ok((5, 6).into()),
Ok((4, 5).into()),
Ok((5, 5).into()),
Ok((6, 5).into()),
];
let actual: Vec<Result<GridPosition, GameError>> = shape.coordinates(&center).collect();
assert_eq!(actual, expected);
}
}

Loading…
Cancel
Save