feat: core gameplay
This commit is contained in:
parent
631ad0c274
commit
87288601ca
@ -16,7 +16,13 @@ pub fn button_system (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn despawn_screen <T: Component> (to_despawn: Query<Entity, With<T>>, mut commands: Commands) {
|
pub fn despawn_screen <T: Component> (to_despawn: Query<Entity, With<T>>, commands: &mut Commands) {
|
||||||
|
for entity in &to_despawn {
|
||||||
|
commands.entity(entity).despawn_recursive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn despawn_screen_move <T: Component> (to_despawn: Query<Entity, With<T>>, mut commands: Commands) {
|
||||||
for entity in &to_despawn {
|
for entity in &to_despawn {
|
||||||
commands.entity(entity).despawn_recursive();
|
commands.entity(entity).despawn_recursive();
|
||||||
}
|
}
|
||||||
|
|||||||
118
src/game.rs
118
src/game.rs
@ -1,11 +1,13 @@
|
|||||||
use crate::prelude::*;
|
use itertools::Itertools;
|
||||||
|
use rand::{distributions::{Distribution, WeightedIndex}, seq::IteratorRandom};
|
||||||
|
|
||||||
|
use crate::{common::despawn_screen, prelude::*};
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct FieldView;
|
pub struct FieldView;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub fn enter (
|
pub fn enter (
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
@ -80,10 +82,10 @@ pub fn enter (
|
|||||||
|
|
||||||
let mut start = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1];
|
let mut start = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1];
|
||||||
start.shuffle(thread);
|
start.shuffle(thread);
|
||||||
*field = Field(start);
|
*field = Field(start, 4);
|
||||||
|
|
||||||
|
|
||||||
render_field(commands, asset_server, field.into());
|
// render_field(commands, asset_server, field.into(), q);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_scoreboard (score: Res<Score>, mut query: Query<&mut Text, With<ScoreboardUi>>) {
|
pub fn update_scoreboard (score: Res<Score>, mut query: Query<&mut Text, With<ScoreboardUi>>) {
|
||||||
@ -95,30 +97,39 @@ pub fn render_field (
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
field: Res<Field>,
|
field: Res<Field>,
|
||||||
|
mut prev_field: ResMut<PrevField>,
|
||||||
|
q: Query<Entity, With<FieldView>>,
|
||||||
) {
|
) {
|
||||||
|
if prev_field.0 != field.0 {
|
||||||
|
despawn_screen::<FieldView>(q, &mut commands);
|
||||||
|
|
||||||
for (idx, &val) in field.iter().enumerate() {
|
for (idx, &val) in field.iter().enumerate() {
|
||||||
if val > 0 {
|
if val > 0 {
|
||||||
spawn_square_at(&mut commands, val, idx, asset_server.clone());
|
spawn_square_at(&mut commands, val, idx, asset_server.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prev_field.0 = field.0.clone();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn spawn_square_at (commands: &mut Commands, level: u32, position: usize, asset_server: AssetServer) {
|
fn spawn_square_at (commands: &mut Commands, level: u8, position: usize, asset_server: AssetServer) {
|
||||||
let font = asset_server.load("proxima_thin.ttf");
|
let font = asset_server.load("proxima_thin.ttf");
|
||||||
let Some(&(x, y)) = sq_origins.get(position) else {
|
let Some(&(x, y)) = sq_origins.get(position) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = 2i32.pow(level);
|
let text = 2i32.pow(level as u32).to_string();
|
||||||
let label = Text2dBundle {
|
let label = Text2dBundle {
|
||||||
text: Text::from_section(text.to_string(), TextStyle {
|
text: Text::from_section(text, TextStyle {
|
||||||
font,
|
font,
|
||||||
font_size: 44.,
|
font_size: 44.,
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
}),
|
}),
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
translation: Vec3::new(x, y, 0.0),
|
translation: Vec3::new(x, y, 1.),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -150,19 +161,21 @@ pub fn process_inputs (
|
|||||||
score: ResMut<Score>,
|
score: ResMut<Score>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
let mover = |dir: MoveDirection| move_field(dir, field, score);
|
||||||
|
|
||||||
for key in kb.get_just_pressed() {
|
for key in kb.get_just_pressed() {
|
||||||
match key {
|
match key {
|
||||||
KeyCode::ArrowUp | KeyCode::KeyW => {
|
KeyCode::ArrowUp | KeyCode::KeyW => {
|
||||||
return move_field(MoveDirection::Up, field, score);
|
return mover(MoveDirection::Up);
|
||||||
},
|
},
|
||||||
KeyCode::ArrowDown | KeyCode::KeyS => {
|
KeyCode::ArrowDown | KeyCode::KeyS => {
|
||||||
return move_field(MoveDirection::Down, field, score);
|
return mover(MoveDirection::Down);
|
||||||
},
|
},
|
||||||
KeyCode::ArrowLeft | KeyCode::KeyA => {
|
KeyCode::ArrowLeft | KeyCode::KeyA => {
|
||||||
return move_field(MoveDirection::Left, field, score);
|
return mover(MoveDirection::Left);
|
||||||
},
|
},
|
||||||
KeyCode::ArrowRight | KeyCode::KeyD => {
|
KeyCode::ArrowRight | KeyCode::KeyD => {
|
||||||
return move_field(MoveDirection::Right, field, score);
|
return mover(MoveDirection::Right);
|
||||||
},
|
},
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
@ -179,28 +192,89 @@ pub fn process_inputs (
|
|||||||
// controls (with custom actuation point)
|
// controls (with custom actuation point)
|
||||||
// https://bevy-cheatbook.github.io/input/gamepad.html#gamepad-settings
|
// https://bevy-cheatbook.github.io/input/gamepad.html#gamepad-settings
|
||||||
if icgbp(GBT::DPadLeft) {
|
if icgbp(GBT::DPadLeft) {
|
||||||
return move_field(MoveDirection::Left, field, score);
|
return mover(MoveDirection::Left);
|
||||||
} else if icgbp(GBT::DPadRight) {
|
} else if icgbp(GBT::DPadRight) {
|
||||||
return move_field(MoveDirection::Right, field, score);
|
return mover(MoveDirection::Right);
|
||||||
} else if icgbp(GBT::DPadUp) {
|
} else if icgbp(GBT::DPadUp) {
|
||||||
return move_field(MoveDirection::Up, field, score);
|
return mover(MoveDirection::Up);
|
||||||
} else if icgbp(GBT::DPadDown) {
|
} else if icgbp(GBT::DPadDown) {
|
||||||
return move_field(MoveDirection::Down, field, score);
|
return mover(MoveDirection::Down);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_field (
|
fn move_field (
|
||||||
direction: MoveDirection,
|
direction: MoveDirection,
|
||||||
field: ResMut<Field>,
|
mut field: ResMut<Field>,
|
||||||
score: ResMut<Score>
|
mut score: ResMut<Score>,
|
||||||
) {
|
) {
|
||||||
|
let (new, score_incr) = calculate_move(&field.0, field.1, direction);
|
||||||
println!("{:?} {:?}", direction, score);
|
if new != field.0 {
|
||||||
|
field.0 = new;
|
||||||
|
score.0 += score_incr;
|
||||||
|
spawn_new_square(field);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redraw_field () {
|
fn calculate_move (source: &[u8], size: usize, dir: MoveDirection) -> (Vec<u8>, usize) {
|
||||||
|
let mut score_changes = vec![];
|
||||||
|
let mut target = vec![0; source.len()];
|
||||||
|
|
||||||
|
let mut qs = Vec::with_capacity(size);
|
||||||
|
|
||||||
|
for x in 0..size {
|
||||||
|
let mut col = Vec::with_capacity(size);
|
||||||
|
for y in 0..size {
|
||||||
|
if dir == MoveDirection::Up || dir == MoveDirection::Down {
|
||||||
|
col.push(y * size + x);
|
||||||
|
} else {
|
||||||
|
col.push(x * size + y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dir == MoveDirection::Down || dir == MoveDirection::Right {
|
||||||
|
col.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
qs.push(col);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for q in qs {
|
||||||
|
let mut write_cursor = 0;
|
||||||
|
let mut cursor = 0;
|
||||||
|
let _in = q.iter().map(|&idx| source[idx]).filter(|&e| e != 0).collect_vec();
|
||||||
|
|
||||||
|
while let Some(&cur) = _in.get(cursor) {
|
||||||
|
let next = _in.get(cursor + 1);
|
||||||
|
|
||||||
|
match (cur, next) {
|
||||||
|
(a, Some(b)) if a == *b => {
|
||||||
|
target[q[write_cursor]] = cur + 1;
|
||||||
|
score_changes.push(cur as usize);
|
||||||
|
write_cursor += 1;
|
||||||
|
cursor += 2;
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
target[q[write_cursor]] = cur;
|
||||||
|
write_cursor += 1;
|
||||||
|
cursor += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
(target, score_changes.iter().sum())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_new_square (mut field: ResMut<Field>) {
|
||||||
|
let rng = &mut thread_rng();
|
||||||
|
let Some(empty) = field.0.iter().enumerate().filter(|&(_, &val)| val == 0).map(|(idx, _)| idx).choose(rng) else { return };
|
||||||
|
let choices = [1, 2, 3];
|
||||||
|
let weights = [8, 4, 1];
|
||||||
|
let Ok(index) = WeightedIndex::new(&weights) else { return };
|
||||||
|
field.0[empty] = choices[index.sample(rng)];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,12 +17,13 @@ impl Plugin for SqPlugin {
|
|||||||
.insert_state(State4096::Game)
|
.insert_state(State4096::Game)
|
||||||
.insert_state(InGameState::Static)
|
.insert_state(InGameState::Static)
|
||||||
.insert_resource(Score(0))
|
.insert_resource(Score(0))
|
||||||
.insert_resource(Field(vec![]))
|
.insert_resource(Field(vec![], 0))
|
||||||
|
.insert_resource(PrevField(vec![]))
|
||||||
.add_systems(Startup, setup::setup)
|
.add_systems(Startup, setup::setup)
|
||||||
.add_systems(Update, (game::update_scoreboard, game::process_inputs).run_if(in_state(State4096::Game)))
|
.add_systems(Update, (game::render_field, game::update_scoreboard, game::process_inputs).run_if(in_state(State4096::Game)))
|
||||||
.add_systems(Update, (menu::actions, common::button_system).run_if(in_state(State4096::Menu)))
|
.add_systems(Update, (menu::actions, common::button_system).run_if(in_state(State4096::Menu)))
|
||||||
.add_systems(OnEnter(State4096::Menu), menu::enter)
|
.add_systems(OnEnter(State4096::Menu), menu::enter)
|
||||||
.add_systems(OnExit(State4096::Menu), common::despawn_screen::<menu::Menu>)
|
.add_systems(OnExit(State4096::Menu), common::despawn_screen_move::<menu::Menu>)
|
||||||
.add_systems(OnEnter(State4096::Game), game::enter)
|
.add_systems(OnEnter(State4096::Game), game::enter)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,10 @@ pub struct ScoreboardUi;
|
|||||||
|
|
||||||
|
|
||||||
#[derive(Resource, Deref, DerefMut)]
|
#[derive(Resource, Deref, DerefMut)]
|
||||||
pub struct Field (pub Vec<u32>);
|
pub struct Field (#[deref] pub Vec<u8>, pub usize);
|
||||||
|
|
||||||
|
#[derive(Resource, Deref, DerefMut)]
|
||||||
|
pub struct PrevField (pub Vec<u8>);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum MoveDirection {
|
pub enum MoveDirection {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user