bounds checking works

main
Elijah Voigt 2 months ago
parent 1dfac5dd61
commit e09ee105ea

@ -2,10 +2,10 @@
## Bugs ## Bugs
* Piece should stop moving when it hits the edge of the grid
## Features ## Features
* Drawing the grid where the piece can go
* Placing pieces in the grid * Placing pieces in the grid
* Placing multiple pieces stacking * Placing multiple pieces stacking
* Clearing lines when they are full * Clearing lines when they are full

@ -32,7 +32,10 @@ impl Plugin for BlocksPlugin {
.init_asset_loader::<ShapeAssetLoader>() .init_asset_loader::<ShapeAssetLoader>()
.add_message::<Movement>() .add_message::<Movement>()
.add_systems(OnEnter(Loading(true)), load_assets.run_if(run_once)) .add_systems(OnEnter(Loading(true)), load_assets.run_if(run_once))
.add_systems(OnEnter(GameState::Setup), (setup_camera, setup_blocks)) .add_systems(
OnEnter(GameState::Setup),
(setup_camera, setup_blocks, setup_grid),
)
.add_systems( .add_systems(
Update, Update,
( (
@ -102,11 +105,42 @@ struct GridPosition {
pub y: usize, pub y: usize,
} }
#[derive(Debug, Error)]
enum GridPositionError {
#[error("X > X_MAX")]
XOver,
#[error("X < 0")]
XUnder,
#[error("Y < 0")]
YUnder,
#[error("Y < 0 && X < 0")]
BothUnder,
}
impl GridPosition { impl GridPosition {
fn with_relative_offset(&self, other: &RelativePosition) -> GridPosition { fn with_relative_offset(&self, other: &RelativePosition) -> GridPosition {
GridPosition { self.add(*other).unwrap()
x: self.x.checked_add_signed(other.x).unwrap(), }
y: self.y.checked_add_signed(other.y).unwrap(),
fn add(&self, other: RelativePosition) -> Result<GridPosition, GridPositionError> {
let x = self.x.checked_add_signed(other.x);
let y = self.y.checked_add_signed(other.y);
match (x, y) {
// Both underflow
(None, None) => Err(GridPositionError::BothUnder),
// Just y underflows
(None, Some(_)) => Err(GridPositionError::XUnder),
// y underflow or x overflow
(Some(x), None) => match x {
0..X_MAX => Err(GridPositionError::YUnder),
_ => Err(GridPositionError::XOver),
}
// both good, may x overflow
(Some(x), Some(y)) => match x {
0..X_MAX => Ok(GridPosition { x, y }),
_ => Err(GridPositionError::XOver),
}
} }
} }
} }
@ -119,31 +153,6 @@ impl Into<Vec3> for &GridPosition {
} }
} }
impl std::ops::AddAssign<RelativePosition> for GridPosition {
fn add_assign(&mut self, rhs: RelativePosition) {
self.x = self.x.strict_add_signed(rhs.x);
self.y = self.y.strict_add_signed(rhs.y);
}
}
impl std::ops::SubAssign<RelativePosition> for GridPosition {
fn sub_assign(&mut self, rhs: RelativePosition) {
self.x = self.x.strict_sub_signed(rhs.x);
self.y = self.y.strict_sub_signed(rhs.y);
}
}
impl std::ops::Add<RelativePosition> for GridPosition {
type Output = GridPosition;
fn add(self, rhs: RelativePosition) -> GridPosition {
GridPosition {
x: self.x.strict_add_signed(rhs.x),
y: self.y.strict_add_signed(rhs.y),
}
}
}
/// Block positions relative to the shape's center /// Block positions relative to the shape's center
#[derive(Component, PartialEq, Debug, Clone, Copy)] #[derive(Component, PartialEq, Debug, Clone, Copy)]
pub(crate) struct RelativePosition { pub(crate) struct RelativePosition {
@ -258,8 +267,9 @@ impl ShapeLayout {
// TODO: Just hard-code this // TODO: Just hard-code this
ShapeLayout { ShapeLayout {
matrix: self.rotated_matrix(Orientation::Right), matrix: self.rotated_matrix(Orientation::Right),
symmetrical: false symmetrical: false,
}.rotated_matrix(Orientation::Right) }
.rotated_matrix(Orientation::Right)
} }
Orientation::Left => { Orientation::Left => {
// The main algorithm // The main algorithm
@ -353,6 +363,7 @@ fn setup_blocks(
server: Res<AssetServer>, server: Res<AssetServer>,
mut checklist: ResMut<SetupChecklist>, mut checklist: ResMut<SetupChecklist>,
) { ) {
// TODO: WARN: THIS WILL CAUSE ISSUES WHEN WE LOAD MULTIPLE SHAPES
let h: Handle<ShapeAsset> = server let h: Handle<ShapeAsset> = server
.get_handle(all_assets.handles[0].path().unwrap()) .get_handle(all_assets.handles[0].path().unwrap())
.unwrap(); .unwrap();
@ -362,6 +373,30 @@ fn setup_blocks(
checklist.spawn_shape = true; checklist.spawn_shape = true;
} }
fn setup_grid(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut checklist: ResMut<SetupChecklist>,
) {
let m: Mesh = Rectangle::new(SCALE, SCALE).into();
let mh: Handle<Mesh> = meshes.add(m);
let m2d = Mesh2d(mh);
let c: Color = WHITE.with_alpha(0.5).into();
let ch: Handle<ColorMaterial> = materials.add(c);
let mat = MeshMaterial2d(ch);
let t = Transform::from_xyz(0.0, 0.0, -1.0);
for x in 0..X_MAX {
for y in 0..Y_MAX {
let gp = GridPosition { x, y };
commands.spawn((gp, m2d.clone(), mat.clone(), t.clone()));
}
}
checklist.spawn_grid = true;
}
/// Blocks <- Shape Relationship /// Blocks <- Shape Relationship
#[derive(Component)] #[derive(Component)]
#[relationship(relationship_target = ShapeBlocks)] #[relationship(relationship_target = ShapeBlocks)]
@ -428,7 +463,9 @@ fn update_grid_position_transform(
>, >,
) { ) {
query.iter_mut().for_each(|(gp, mut t)| { query.iter_mut().for_each(|(gp, mut t)| {
t.translation = gp.into(); let new_t: Vec3 = gp.into();
t.translation.x = new_t.x;
t.translation.y = new_t.y;
}); });
} }
@ -440,19 +477,22 @@ fn propogate_grid_position(
parent.iter().for_each(|(parent_gp, sbs)| { parent.iter().for_each(|(parent_gp, sbs)| {
sbs.iter().for_each(|e| { sbs.iter().for_each(|e| {
let (mut gp, rp) = children.get_mut(e).unwrap(); let (mut gp, rp) = children.get_mut(e).unwrap();
*gp = parent_gp.clone() + *rp; *gp = parent_gp.with_relative_offset(rp);
}); });
}); });
} }
/// When a block's relative position changes, update it's grid position /// When a block's relative position changes, update it's grid position
fn propogate_relative_position( fn propogate_relative_position(
mut children: Query<(&mut GridPosition, &RelativePosition, &ShapeBlock), Changed<RelativePosition>>, mut children: Query<
(&mut GridPosition, &RelativePosition, &ShapeBlock),
Changed<RelativePosition>,
>,
parent: Query<&GridPosition, Without<ShapeBlock>>, parent: Query<&GridPosition, Without<ShapeBlock>>,
) { ) {
children.iter_mut().for_each(|(mut gp, rp, sb)| { children.iter_mut().for_each(|(mut gp, rp, sb)| {
let parent_gp = parent.get(sb.0).unwrap(); let parent_gp = parent.get(sb.0).unwrap();
*gp = parent_gp.clone() + *rp; *gp = parent_gp.with_relative_offset(rp);
}); });
} }
@ -472,7 +512,7 @@ fn propogate_orientation(
} }
/// Movement message used to propose/plan a move /// Movement message used to propose/plan a move
#[derive(Debug, Message)] #[derive(Debug, Message, PartialEq)]
enum Movement { enum Movement {
Rotate, Rotate,
Left, Left,
@ -532,33 +572,41 @@ impl Orientation {
fn handle_movement( fn handle_movement(
mut moves: MessageReader<Movement>, mut moves: MessageReader<Movement>,
mut grid_positions: Query<&mut GridPosition, With<ShapeBlocks>>, mut shapes: Query<(&ShapeLayout, &mut GridPosition, &mut Orientation)>,
mut orientations: Query<&mut Orientation, With<ShapeBlocks>>,
) { ) {
moves.read().for_each(|m| match m { moves.read().for_each(|m| {
Movement::Left => { shapes.iter_mut().for_each(|(shape_layout, mut gp, mut o)| {
grid_positions.iter_mut().for_each(|mut gp| { // First determine if proposed positions are valid
debug!("moving shape left"); // Determine next orientation
*gp -= RelativePosition::new(1, 0); let proposed_orientation = if *m == Movement::Rotate {
}); o.rotated()
} } else {
Movement::Right => { *o
grid_positions.iter_mut().for_each(|mut gp| { };
debug!("moving shape right");
*gp += RelativePosition::new(1, 0); // And next position
}); let proposed_position = match m {
} Movement::Left => gp.add((-1, 0).into()),
Movement::Down => { Movement::Right => gp.add((1, 0).into()),
grid_positions.iter_mut().for_each(|mut gp| { Movement::Down => gp.add((0, -1).into()),
debug!("moving shape down"); Movement::Rotate => Ok(gp.clone()),
*gp -= RelativePosition::new(0, 1); };
});
} if let Ok(pp) = proposed_position {
Movement::Rotate => { // Check that new positions are all valid
orientations.iter_mut().for_each(|mut o| { if shape_layout
debug!("rotating shape"); .positions(proposed_orientation)
*o = o.rotated(); .iter()
}); .all(|rp| pp.add(*rp).is_ok()) {
} // then commit the movement
}) *gp = pp;
*o = proposed_orientation;
} else {
// invalid position!
}
} else {
// Invalid move!
}
});
});
} }

@ -25,7 +25,7 @@ impl Plugin for DebugPlugin {
log_transition::<DebugState>.run_if(state_changed::<DebugState>), log_transition::<DebugState>.run_if(state_changed::<DebugState>),
log_transition::<Loading>.run_if(state_changed::<Loading>), log_transition::<Loading>.run_if(state_changed::<Loading>),
log_transition::<GameState>.run_if(state_changed::<GameState>), log_transition::<GameState>.run_if(state_changed::<GameState>),
log_transition::<DebugOutlines>.run_if(state_changed::<DebugOutlines>) log_transition::<DebugOutlines>.run_if(state_changed::<DebugOutlines>),
), ),
) )
.add_systems( .add_systems(
@ -45,11 +45,11 @@ impl Plugin for DebugPlugin {
sync_state_to_ui::<DebugOutlines>, sync_state_to_ui::<DebugOutlines>,
) )
.run_if(state_changed::<DebugOutlines>), .run_if(state_changed::<DebugOutlines>),
) ),
) )
.add_systems( .add_systems(
Update, Update,
draw_outline_gizmos.run_if(in_state(DebugOutlines(true))) draw_outline_gizmos.run_if(in_state(DebugOutlines(true))),
); );
} }
} }
@ -100,21 +100,15 @@ fn setup_ui(mut commands: Commands) {
..default() ..default()
}, },
DebugState(true), DebugState(true),
children![ children![(
( Node { ..default() },
Node { children![(
..default() DebugState(true),
}, checkbox((), Spawn((Text::new("outlines"), ThemedText))),
children![ observe(toggle_outline_state),
( observe(check_box),
DebugState(true), ),]
checkbox((), Spawn((Text::new("outlines"), ThemedText))), ),],
observe(toggle_outline_state),
observe(check_box),
),
]
),
]
)); ));
commands.spawn(( commands.spawn((
@ -169,10 +163,7 @@ impl fmt::Display for DebugOutlines {
} }
} }
fn toggle_outline_state( fn toggle_outline_state(event: On<ValueChange<bool>>, mut next: ResMut<NextState<DebugOutlines>>) {
event: On<ValueChange<bool>>,
mut next: ResMut<NextState<DebugOutlines>>,
) {
next.set(DebugOutlines(event.event().value)); next.set(DebugOutlines(event.event().value));
} }

@ -97,14 +97,17 @@ struct AllAssets {
} }
/// A "checklist" to know if we can progress from setup to the game /// A "checklist" to know if we can progress from setup to the game
/// TODO: Turn this into a hash-map
/// Systems on sartup insert `key:false` and update to `key;true`
#[derive(Default, Resource)] #[derive(Default, Resource)]
struct SetupChecklist { struct SetupChecklist {
spawn_shape: bool, spawn_shape: bool,
spawn_grid: bool,
} }
impl SetupChecklist { impl SetupChecklist {
fn done(&self) -> bool { fn done(&self) -> bool {
self.spawn_shape self.spawn_shape && self.spawn_grid
} }
} }

@ -26,7 +26,7 @@ fn test_shape_layout_01c() {
let actual = ShapeLayout::new(vec![vec![0, 1, 0], vec![1, 1, 1]]).positions(Orientation::Down); let actual = ShapeLayout::new(vec![vec![0, 1, 0], vec![1, 1, 1]]).positions(Orientation::Down);
let expected: [RelativePosition; 4] = let expected: [RelativePosition; 4] =
[(0, -1).into(), (-1, 0).into(), (0, 0).into(), (1, 0).into(),]; [(0, -1).into(), (-1, 0).into(), (0, 0).into(), (1, 0).into()];
debug_assert_eq!(expected, actual); debug_assert_eq!(expected, actual);
} }

Loading…
Cancel
Save