bounds checking works

main
Elijah Voigt 2 months ago
parent 1dfac5dd61
commit e09ee105ea

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

@ -32,7 +32,10 @@ impl Plugin for BlocksPlugin {
.init_asset_loader::<ShapeAssetLoader>()
.add_message::<Movement>()
.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(
Update,
(
@ -102,11 +105,42 @@ struct GridPosition {
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 {
fn with_relative_offset(&self, other: &RelativePosition) -> GridPosition {
GridPosition {
x: self.x.checked_add_signed(other.x).unwrap(),
y: self.y.checked_add_signed(other.y).unwrap(),
self.add(*other).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
#[derive(Component, PartialEq, Debug, Clone, Copy)]
pub(crate) struct RelativePosition {
@ -258,8 +267,9 @@ impl ShapeLayout {
// TODO: Just hard-code this
ShapeLayout {
matrix: self.rotated_matrix(Orientation::Right),
symmetrical: false
}.rotated_matrix(Orientation::Right)
symmetrical: false,
}
.rotated_matrix(Orientation::Right)
}
Orientation::Left => {
// The main algorithm
@ -353,6 +363,7 @@ fn setup_blocks(
server: Res<AssetServer>,
mut checklist: ResMut<SetupChecklist>,
) {
// TODO: WARN: THIS WILL CAUSE ISSUES WHEN WE LOAD MULTIPLE SHAPES
let h: Handle<ShapeAsset> = server
.get_handle(all_assets.handles[0].path().unwrap())
.unwrap();
@ -362,6 +373,30 @@ fn setup_blocks(
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
#[derive(Component)]
#[relationship(relationship_target = ShapeBlocks)]
@ -428,7 +463,9 @@ fn update_grid_position_transform(
>,
) {
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)| {
sbs.iter().for_each(|e| {
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
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>>,
) {
children.iter_mut().for_each(|(mut gp, rp, sb)| {
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
#[derive(Debug, Message)]
#[derive(Debug, Message, PartialEq)]
enum Movement {
Rotate,
Left,
@ -532,33 +572,41 @@ impl Orientation {
fn handle_movement(
mut moves: MessageReader<Movement>,
mut grid_positions: Query<&mut GridPosition, With<ShapeBlocks>>,
mut orientations: Query<&mut Orientation, With<ShapeBlocks>>,
mut shapes: Query<(&ShapeLayout, &mut GridPosition, &mut Orientation)>,
) {
moves.read().for_each(|m| match m {
Movement::Left => {
grid_positions.iter_mut().for_each(|mut gp| {
debug!("moving shape left");
*gp -= RelativePosition::new(1, 0);
});
moves.read().for_each(|m| {
shapes.iter_mut().for_each(|(shape_layout, mut gp, mut o)| {
// First determine if proposed positions are valid
// Determine next orientation
let proposed_orientation = if *m == Movement::Rotate {
o.rotated()
} else {
*o
};
// And next position
let proposed_position = match m {
Movement::Left => gp.add((-1, 0).into()),
Movement::Right => gp.add((1, 0).into()),
Movement::Down => gp.add((0, -1).into()),
Movement::Rotate => Ok(gp.clone()),
};
if let Ok(pp) = proposed_position {
// Check that new positions are all valid
if shape_layout
.positions(proposed_orientation)
.iter()
.all(|rp| pp.add(*rp).is_ok()) {
// then commit the movement
*gp = pp;
*o = proposed_orientation;
} else {
// invalid position!
}
Movement::Right => {
grid_positions.iter_mut().for_each(|mut gp| {
debug!("moving shape right");
*gp += RelativePosition::new(1, 0);
});
} else {
// Invalid move!
}
Movement::Down => {
grid_positions.iter_mut().for_each(|mut gp| {
debug!("moving shape down");
*gp -= RelativePosition::new(0, 1);
});
}
Movement::Rotate => {
orientations.iter_mut().for_each(|mut o| {
debug!("rotating shape");
*o = o.rotated();
});
}
})
}

@ -25,7 +25,7 @@ impl Plugin for DebugPlugin {
log_transition::<DebugState>.run_if(state_changed::<DebugState>),
log_transition::<Loading>.run_if(state_changed::<Loading>),
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(
@ -45,11 +45,11 @@ impl Plugin for DebugPlugin {
sync_state_to_ui::<DebugOutlines>,
)
.run_if(state_changed::<DebugOutlines>),
)
),
)
.add_systems(
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()
},
DebugState(true),
children![
(
Node {
..default()
},
children![
(
children![(
Node { ..default() },
children![(
DebugState(true),
checkbox((), Spawn((Text::new("outlines"), ThemedText))),
observe(toggle_outline_state),
observe(check_box),
),
]
),
]
),]
),],
));
commands.spawn((
@ -169,10 +163,7 @@ impl fmt::Display for DebugOutlines {
}
}
fn toggle_outline_state(
event: On<ValueChange<bool>>,
mut next: ResMut<NextState<DebugOutlines>>,
) {
fn toggle_outline_state(event: On<ValueChange<bool>>, mut next: ResMut<NextState<DebugOutlines>>) {
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
/// TODO: Turn this into a hash-map
/// Systems on sartup insert `key:false` and update to `key;true`
#[derive(Default, Resource)]
struct SetupChecklist {
spawn_shape: bool,
spawn_grid: bool,
}
impl SetupChecklist {
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 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);
}

Loading…
Cancel
Save