Compare commits

..

6 Commits

Author SHA1 Message Date
Elijah Voigt fb535106ca Display shape and orientation for debugging 2 weeks ago
Elijah Voigt 78854196a1 Rotations look mooooostly right 2 weeks ago
Elijah Voigt 7ae68f0e4c The basic idea of "Move the tiles instead of rotating the shapes"
It is very buggy, but you get the just if what I'm going for.

I'll fix it up tomorrow.
2 weeks ago
Elijah Voigt c62350e382 Spawning pieces at top of board by default 3 weeks ago
Elijah Voigt 459740a0ef Add automatic falling 3 weeks ago
Elijah Voigt f89d52bfbf Add bounds checking for pieces 3 weeks ago

@ -1,5 +1,12 @@
// Bevy basically forces "complex types" with Querys
#![allow(clippy::type_complexity)]
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
fn main() { fn main() {
App::new() App::new()
.add_plugins(BaseGamePlugin { .add_plugins(BaseGamePlugin {
@ -8,13 +15,28 @@ fn main() {
game_type: GameType::Two, game_type: GameType::Two,
..default() ..default()
}) })
.add_systems(Startup, init_pieces) .init_state::<Falling>()
.add_systems(Startup, (init_pieces, init_debug_ui))
.add_systems( .add_systems(
Update, Update,
( (
kb_movement.run_if(on_event::<KeyboardInput>), kb_input.run_if(on_event::<KeyboardInput>),
update_position, update_position,
update_orientation, falling
.run_if(in_state(Falling::On))
.run_if(clock_cycle(1.0)),
set_piece
.run_if(any_component_added::<Shape>
.or(any_component_changed::<Shape>)
.or(any_component_added::<Orientation>)
.or(any_component_changed::<Orientation>)
),
set_relative_piece_positions.run_if(
any_component_added::<RelativePosition>
.or(any_component_changed::<RelativePosition>),
),
sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>),
sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>),
), ),
) )
.add_systems(Update, draw_grid) .add_systems(Update, draw_grid)
@ -23,21 +45,117 @@ fn main() {
const SCALE: f32 = 30.0; const SCALE: f32 = 30.0;
#[derive(Component, Default, Debug, Clone, Copy)] /// A shape, e.g., the long piece
#[derive(Component, Debug, Default)]
enum Shape {
#[default]
O,
T,
L,
J,
S,
Z,
I,
}
impl Display for Shape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Shape::O => write!(f, "O"),
Shape::T => write!(f, "T"),
Shape::L => write!(f, "L"),
Shape::J => write!(f, "J"),
Shape::S => write!(f, "S"),
Shape::Z => write!(f, "Z"),
Shape::I => write!(f, "I"),
}
}
}
/// A part of a piece, i.e., a single square of a piece
#[derive(Component, Debug)]
struct ShapePiece;
#[derive(Component, Debug)]
struct RelativePosition {
x: i8,
y: i8,
}
impl From<(i8, i8)> for RelativePosition {
fn from((x, y): (i8, i8)) -> RelativePosition {
RelativePosition { x, y }
}
}
#[derive(Component, Debug, Clone, Copy, PartialEq)]
#[require(Transform, Visibility)] #[require(Transform, Visibility)]
struct GridPosition { struct GridPosition {
x: isize, x: usize,
y: isize, y: usize,
}
impl GridPosition {
fn move_up(&self) -> Self {
Self {
y: if self.y + 1 < 20 {
self.y.saturating_add(1)
} else {
self.y
},
x: self.x,
}
}
fn move_down(&mut self) -> Self {
Self {
y: self.y.saturating_sub(1),
x: self.x,
}
}
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 < 10 {
self.x.saturating_add(1)
} else {
self.x
},
y: self.y,
}
}
}
impl Default for GridPosition {
fn default() -> Self {
GridPosition { x: 5, y: 20 }
}
} }
impl From<&GridPosition> for Vec3 { impl From<&GridPosition> for Vec3 {
fn from(GridPosition { x, y }: &GridPosition) -> Vec3 { fn from(GridPosition { x, y }: &GridPosition) -> Vec3 {
Vec3::new((*x as f32) * SCALE, (*y as f32) * SCALE, 0.0) // Grid Positions start in the bottom left of the area
// 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 = x_0 + ((*x as f32) * SCALE);
let y_0 = -SCALE * 10.0 + (0.5 * SCALE);
let y = y_0 + ((*y as f32) * SCALE);
Vec3::new(x, y, 0.0)
} }
} }
impl From<(isize, isize)> for GridPosition { impl From<(usize, usize)> for GridPosition {
fn from((x, y): (isize, isize)) -> GridPosition { fn from((x, y): (usize, usize)) -> GridPosition {
GridPosition { x, y } GridPosition { x, y }
} }
} }
@ -53,6 +171,12 @@ impl std::ops::Add for GridPosition {
} }
} }
impl std::ops::AddAssign<&GridPosition> for GridPosition {
fn add_assign(&mut self, rhs: &GridPosition) {
*self = *self + *rhs;
}
}
#[derive(Component, Default, Debug)] #[derive(Component, Default, Debug)]
enum Orientation { enum Orientation {
#[default] #[default]
@ -82,88 +206,272 @@ impl Orientation {
} }
} }
impl From<&Orientation> for Quat { impl Display for Orientation {
fn from(other: &Orientation) -> Quat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let z = match other { match self {
Orientation::Up => 0.0, Orientation::Up => write!(f, "up"),
Orientation::Left => -PI * 0.5, Orientation::Down => write!(f, "down"),
Orientation::Down => -PI, Orientation::Left => write!(f, "<-"),
Orientation::Right => -PI * 1.5, Orientation::Right => write!(f, "->"),
}; }
Quat::from_rotation_z(z)
} }
} }
#[derive(States, Clone, Eq, PartialEq, Debug, Hash, Default, Component)]
enum Falling {
#[default]
On,
Off,
}
#[derive(Resource, Debug)]
struct Visuals {
material: Handle<ColorMaterial>,
mesh: Handle<Mesh>,
}
fn init_pieces( fn init_pieces(
mut commands: Commands, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,
) { ) {
commands commands.insert_resource(Visuals {
.spawn((Orientation::default(), GridPosition::default())) material: materials.add(ColorMaterial {
.with_children(|parent| { color: WHITE.into(),
let mat = materials.add(ColorMaterial { ..default()
color: WHITE.into(), }),
..default() mesh: meshes.add(Rectangle::new(SCALE, SCALE)),
});
commands.spawn((Orientation::default(), GridPosition::default(), Shape::T));
}
fn init_debug_ui(
mut commands: Commands,
) {
commands.spawn((Node {
top: Val::Px(0.0),
left: Val::Px(0.0),
..default()
}, DebuggingState::On)).with_children(|parent| {
parent.spawn((
Node::default(),
children![
(Text::new("SHAPE"), SyncSingleton::<Shape>::default()),
(Text::new("ORIENTATION"), SyncSingleton::<Orientation>::default()),
]
));
});
}
fn set_piece(
query: Query<(Entity, &Shape, &Orientation), Or<(Added<Shape>, Changed<Shape>, Added<Orientation>, Changed<Orientation>)>>,
mut commands: Commands,
visuals: Res<Visuals>,
) {
query.iter().for_each(|(e, s, o)| {
debug!("{e:?} {s:?} {o:?}");
commands
.entity(e)
.despawn_related::<Children>()
.with_children(|parent| {
let mesh = visuals.mesh.clone();
let mat = visuals.material.clone();
#[rustfmt::skip]
let piece_positions: [RelativePosition;4] = match s {
Shape::O => [
(0,1).into(),(1,1).into(),
(0,0).into(),(1,0).into()
],
Shape::T => match o {
Orientation::Up => [
(0,1).into(),
(-1,0).into(),(0,0).into(),(1,0).into(),
],
Orientation::Down => [
(-1,0).into(),(0,0).into(),(1,0).into(),
(0,-1).into(),
],
Orientation::Right => [
(0,1).into(),
(0,0).into(), (1,0).into(),
(0,-1).into(),
],
Orientation::Left => [
(0,1).into(),
(-1,0).into(),(0,0).into(),
(0,-1).into(),
]
},
Shape::L => match o {
Orientation::Up => [
(0,1).into(),
(0,0).into(),
(0,-1).into(),(1,-1).into(),
],
Orientation::Down => [
(-1,1).into(),(0,1).into(),
(0,0).into(),
(0,-1).into(),
],
Orientation::Right => [
(-1,0).into(),(0,0).into(),(1,0).into(),
(-1,-1).into(),
],
Orientation::Left => [
(1,1).into(),
(-1,0).into(),(0,0).into(),(1,0).into(),
],
},
Shape::J => match o {
Orientation::Up => [
(0,1).into(),
(0,0).into(),
(-1,-1).into(),(0,-1).into(),
],
Orientation::Down => [
(0,1).into(),(1,1).into(),
(0,0).into(),
(0,-1).into(),
],
Orientation::Left => [
(-1,0).into(),(0,0).into(),(1,0).into(),
(1,-1).into(),
],
Orientation::Right => [
(-1,1).into(),
(-1,0).into(),(0,0).into(),(1,0).into()
],
},
Shape::S => match o {
Orientation::Up => [
(0,0).into(),(1,0).into(),
(-1,-1).into(),(0,-1).into(),
],
Orientation::Down => [
(0,1).into(),(1,1).into(),
(-1,0).into(),(0,0).into(),
],
Orientation::Right => [
(-1,1).into(),
(-1,0).into(),(0,0).into(),
(0,-1).into(),
],
Orientation::Left => [
(0,1).into(),
(0,0).into(),(1,0).into(),
(1,-1).into(),
],
},
Shape::Z => match o {
Orientation::Up => [
(-1,0).into(),(0,0).into(),
(0,-1).into(),(1,-1).into(),
],
Orientation::Down => [
(-1,1).into(),(0,1).into(),
(0,0).into(),(1,0).into(),
],
Orientation::Left => [
(1,1).into(),
(0,0).into(),(1,0).into(),
(0,-1).into(),
],
Orientation::Right => [
(0,1).into(),
(-1,0).into(),(0,0).into(),
(-1,-1).into(),
],
},
// TODO: This does not match tetris!
Shape::I => match o {
Orientation::Up => [
(0,2).into(),
(0,1).into(),
(0,0).into(),
(0,-1).into(),
],
Orientation::Down => [
(-1,2).into(),
(-1,1).into(),
(-1,0).into(),
(-1,-1).into(),
],
Orientation::Left => [
(-2,0).into(),(-1,0).into(),(0,0).into(),(1,0).into(),
],
Orientation::Right => [
(-2,1).into(),(-1,1).into(),(0,1).into(),(1,1).into(),
]
}
};
piece_positions.into_iter().for_each(|rp| {
parent.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp));
}); });
parent.spawn((
Mesh2d(meshes.add(Rectangle::new(SCALE, SCALE))),
MeshMaterial2d(mat.clone()),
Transform::from_xyz(0.0, 0.0, 0.0),
));
parent.spawn((
Mesh2d(meshes.add(Rectangle::new(SCALE, SCALE))),
MeshMaterial2d(mat.clone()),
Transform::from_xyz(SCALE, 0.0, 0.0),
));
parent.spawn((
Mesh2d(meshes.add(Rectangle::new(SCALE, SCALE))),
MeshMaterial2d(mat.clone()),
Transform::from_xyz(0.0, SCALE, 0.0),
));
}); });
});
} }
fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed<GridPosition>>) { fn set_relative_piece_positions(
query.iter_mut().for_each(|(gp, mut t)| { query: Query<
let tmp: Vec3 = gp.into(); (Entity, &RelativePosition),
debug!("Updating position {:?}", tmp); Or<(Added<RelativePosition>, Changed<RelativePosition>)>,
>,
t.translation = gp.into(); mut commands: Commands,
) {
query.iter().for_each(|(e, rp)| {
commands.entity(e).insert(Transform::from_xyz(
(rp.x as f32) * SCALE,
(rp.y as f32) * SCALE,
0.0,
));
}); });
} }
fn update_orientation(mut query: Query<(&Orientation, &mut Transform), Changed<Orientation>>) { fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed<GridPosition>>) {
query.iter_mut().for_each(|(o, mut t)| { query.iter_mut().for_each(|(gp, mut t)| {
t.rotation = o.into(); t.translation = gp.into();
debug!("Setting orientation to {:?}", o); debug!("Updating position {:?}", t.translation);
}); });
} }
fn kb_movement(mut events: EventReader<KeyboardInput>, mut query: Query<(&mut GridPosition, &mut Orientation)>) { fn kb_input(
mut events: EventReader<KeyboardInput>,
mut query: Query<(&mut GridPosition, &mut Orientation, &mut Shape)>,
curr: Res<State<Falling>>,
mut next: ResMut<NextState<Falling>>,
) {
events.read().for_each( events.read().for_each(
|KeyboardInput { |KeyboardInput {
key_code, state, .. key_code, state, ..
}| { }| {
if let ButtonState::Pressed = state { if let ButtonState::Pressed = state {
let diff: GridPosition = match key_code { // TODO: Restict movement based on size/orientation of piece
KeyCode::ArrowUp => (0, 1), // Check if children would be outside play area...
KeyCode::ArrowDown => (0, -1), query.iter_mut().for_each(|(mut gp, mut o, mut s)| {
KeyCode::ArrowLeft => (-1, 0), match key_code {
KeyCode::ArrowRight => (1, 0), // Up arrow should rotate if in falling mode
_ => (0, 0), // Only move up if in falling::off mode
} KeyCode::ArrowUp => *o = o.prev(),
.into(); KeyCode::ArrowDown => *gp = gp.move_down(),
query.iter_mut().for_each(|(mut gp, _)| { KeyCode::ArrowLeft => *gp = gp.move_left(),
debug!("Moving by {:?}", diff); KeyCode::ArrowRight => *gp = gp.move_right(),
*gp = *gp + diff; KeyCode::Space => next.set(match curr.get() {
Falling::On => Falling::Off,
Falling::Off => Falling::On,
}),
KeyCode::Digit1 => *s = Shape::T,
KeyCode::Digit2 => *s = Shape::O,
KeyCode::Digit3 => *s = Shape::L,
KeyCode::Digit4 => *s = Shape::J,
KeyCode::Digit5 => *s = Shape::S,
KeyCode::Digit6 => *s = Shape::Z,
KeyCode::Digit7 => *s = Shape::I,
_ => (),
}
}); });
if let KeyCode::Enter = key_code {
query.iter_mut().for_each(|(_, mut o)| {
*o = o.next();
});
}
} }
}, },
); );
@ -179,3 +487,27 @@ fn draw_grid(mut gizmos: Gizmos) {
) )
.outer_edges(); .outer_edges();
} }
fn falling(mut query: Query<&mut GridPosition, With<Shape>>) {
query.iter_mut().for_each(|mut gp| {
let next = gp.move_down();
if next != *gp {
*gp = next;
} else {
// Remove the falling component from this entity
}
});
}
// Run condition that returns `true` every `n` seconds
fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
move |t: Res<Time>, mut buf: Local<f32>| -> bool {
*buf += t.delta_secs();
if *buf > n {
*buf = 0.0;
true
} else {
false
}
}
}

@ -175,6 +175,23 @@ pub fn sync_resource_to_ui<R: Resource + Default + Display>(
}); });
} }
/// Marker component for handling Resource -> Ui Sync
#[derive(Component, Default, Debug)]
pub struct SyncSingleton<C: Component + Default + Display>(C);
/// Sync a singleton entity's component to the UI
///
/// Mostly useful for quick n' dirty getting data to the user
pub fn sync_singleton_to_ui<C: Component + Default + Display>(
mut q: Query<(&mut Text, &mut Visibility), With<SyncSingleton<C>>>,
c: Single<&C>,
) {
q.iter_mut().for_each(|(mut t, mut v)| {
t.0 = format!("{}", *c);
*v = Visibility::Inherited;
});
}
/// Updates the scroll position of scrollable nodes in response to mouse input /// Updates the scroll position of scrollable nodes in response to mouse input
pub fn scroll(trigger: Trigger<Pointer<Scroll>>, mut scrollers: Query<&mut ScrollPosition>) { pub fn scroll(trigger: Trigger<Pointer<Scroll>>, mut scrollers: Query<&mut ScrollPosition>) {
let Pointer { let Pointer {

Loading…
Cancel
Save