use std::sync::Arc;
use std::time::Duration;
use bevy::prelude::*;
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
use bevy::sprite::{Material2d, Material2dPlugin, MaterialMesh2dBundle};
use crate::engine::{Engine, Game, Turn};
use crate::GameState;
#[derive(Default, Resource)]
struct Lobby {
engine: Arc<Engine>,
games: Vec<Entity>,
}
#[derive(Component)]
struct Controlled;
#[derive(Component, Clone)]
struct Grid {
shape: (usize, usize),
tiles: Vec2,
center: Vec2,
}
impl Grid {
fn new(shape: (usize, usize)) -> Self {
let (width, height) = shape;
let tiles = Vec2::new(width as f32, height as f32 / 2.);
let center = tiles / 2. - 0.5;
Grid {
shape,
tiles,
center,
}
}
}
#[derive(Component)]
struct Cell {
x: usize,
y: usize,
}
#[derive(Component)]
struct Part {
i: usize,
}
#[derive(Component)]
struct Ghost;
#[derive(Debug, Clone, Asset, TypePath, AsBindGroup)]
struct BoardMaterial {
#[uniform(0)]
color: Color,
#[uniform(1)]
tiles: Vec4,
}
impl BoardMaterial {
fn new(color: Color, tiles: Vec2) -> Self {
Self {
color,
tiles: (tiles, Vec2::ZERO).into(),
}
}
}
impl Material2d for BoardMaterial {
fn fragment_shader() -> ShaderRef {
"shaders/board.wgsl".into()
}
}
fn setup(
engine: Res<Engine>,
mut cmd: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut boards: ResMut<Assets<BoardMaterial>>,
ass: Res<AssetServer>,
mut events: EventWriter<GameEvent>,
) {
let grid = Grid::new(engine.shape);
cmd.insert_resource(GameAssets {
mino_sprite: ass.load("mino.png"),
board_mesh: meshes.add(Mesh::from(shape::Quad::new(grid.tiles * 60.))),
grid_material: boards.add(BoardMaterial::new(Color::rgb(0.2, 0.2, 0.2), grid.tiles)),
outline_material: boards.add(BoardMaterial::new(
Color::rgb(0.8, 0.8, 0.8),
Vec2::splat(1.),
)),
});
cmd.init_resource::<Lobby>();
events.send(GameEvent::Create {
controlled: true,
grid: grid.clone(),
});
events.send(GameEvent::Create {
controlled: false,
grid,
});
}
#[derive(Resource)]
struct GameAssets {
mino_sprite: Handle<Image>,
board_mesh: Handle<Mesh>,
grid_material: Handle<BoardMaterial>,
outline_material: Handle<BoardMaterial>,
}
#[derive(Event)]
enum GameEvent {
Create { controlled: bool, grid: Grid },
Remove { entity: Entity },
}
fn manage_games(
mut cmd: Commands,
assets: Res<GameAssets>,
mut lobby: ResMut<Lobby>,
mut events: EventReader<GameEvent>,
) {
for event in events.read() {
match event {
GameEvent::Create { controlled, grid } => {
let (width, height) = grid.shape;
let center = grid.center;
let sprite = SpriteBundle {
sprite: Sprite {
color: Color::rgba(0., 0., 0., 0.),
..Default::default()
},
texture: assets.mino_sprite.clone(),
..Default::default()
};
let game = Game::new(lobby.engine.clone());
let mut parent = cmd.spawn((game, grid.clone(), SpatialBundle::default()));
lobby.games.push(parent.id());
if *controlled {
parent.insert(Controlled);
}
parent.with_children(|cmd| {
for x in 0..width {
for y in 0..height {
let mut sprite = sprite.clone();
sprite.transform.translation =
((Vec2::new(x as f32, y as f32) - center) * 60.).extend(0.);
cmd.spawn((sprite, Cell { x, y }));
}
}
for i in 0..4 {
cmd.spawn((sprite.clone(), Part { i }));
cmd.spawn((sprite.clone(), Part { i }, Ghost));
}
cmd.spawn(MaterialMesh2dBundle {
mesh: assets.board_mesh.clone().into(),
material: assets.grid_material.clone(),
..Default::default()
});
cmd.spawn(MaterialMesh2dBundle {
mesh: assets.board_mesh.clone().into(),
material: assets.outline_material.clone(),
..Default::default()
});
});
}
GameEvent::Remove { entity } => {
cmd.entity(*entity).despawn_recursive();
lobby.games.retain(|e| e != entity);
}
}
}
}
fn reorder_games(
lobby: Res<Lobby>,
camera: Query<(&Camera, &GlobalTransform)>,
mut games: Query<&mut Transform, With<Game>>,
) {
let (cam, trans) = camera.single();
let width = cam.ndc_to_world(trans, Vec3::X).unwrap().x * 2.;
for (i, game) in lobby.games.iter().enumerate() {
if let Ok(mut transform) = games.get_mut(*game) {
transform.translation = match lobby.games.len() {
1 => Vec3::ZERO,
c => Vec3::X * space_evenly(c, width, 660., i),
};
}
}
}
fn space_evenly(count: usize, width: f32, item_width: f32, i: usize) -> f32 {
let c = count as f32;
(width - item_width * c) / (c + 1.) * (i as f32 + 1.) + item_width * (i as f32 + 0.5)
- width / 2.
}
fn update_tiles(
games: Query<(&Game, &Children)>,
mut cells: Query<(&Cell, &mut Sprite), Without<Part>>,
mut parts: Query<(Option<&Ghost>, &mut Sprite), With<Part>>,
) {
for (game, children) in &games {
for child in children {
if let Ok((cell, mut sprite)) = cells.get_mut(*child) {
sprite.color = Color::from(&game.board[cell.y][cell.x]);
}
if let Ok((ghost, mut sprite)) = parts.get_mut(*child) {
sprite.color = if ghost.is_some() {
Color::rgba(0.9, 0.9, 0.9, 0.8)
} else {
Color::from(&game.piece.kind)
};
}
}
}
}
fn update_pieces(
games: Query<(&Game, &Grid, &Children)>,
mut parts: Query<(&Part, &mut Transform), Without<Ghost>>,
mut ghosts: Query<(&Part, &mut Transform), With<Ghost>>,
) {
for (game, grid, children) in &games {
let center = grid.center;
let ghost = game.ghost();
for child in children {
if let Ok((part, mut transform)) = parts.get_mut(*child) {
let (x, y) = game.piece.parts[part.i];
transform.translation = ((Vec2::new(x as f32, y as f32) - center) * 60.).extend(0.);
}
if let Ok((part, mut transform)) = ghosts.get_mut(*child) {
let (x, mut y) = game.piece.parts[part.i];
y -= ghost;
transform.translation = ((Vec2::new(x as f32, y as f32) - center) * 60.).extend(0.);
}
}
}
}
fn kill_players(games: Query<(Entity, &Game)>, mut events: EventWriter<GameEvent>) {
for (entity, game) in &games {
if game.lockout {
events.send(GameEvent::Remove { entity });
}
}
}
struct AutoShift {
das: Timer,
arr: Timer,
}
impl Default for AutoShift {
fn default() -> Self {
let mut das = Timer::new(Duration::from_millis(150), TimerMode::Once);
let arr = Timer::new(Duration::from_millis(33), TimerMode::Repeating);
das.pause();
Self { das, arr }
}
}
fn handle_input(
mut game: Query<&mut Game, With<Controlled>>,
keys: Res<Input<KeyCode>>,
time: Res<Time>,
mut shift: Local<AutoShift>,
) {
let Ok(mut game) = game.get_single_mut() else {
return;
};
shift.das.tick(time.delta());
shift.arr.tick(time.delta());
if keys.just_pressed(KeyCode::A) {
shift.das.reset();
shift.das.unpause();
game.translate(-1, 0);
}
if keys.just_pressed(KeyCode::D) {
shift.das.reset();
shift.das.unpause();
game.translate(1, 0);
}
if shift.das.just_finished() {
shift.arr.reset();
}
if shift.das.just_finished() || shift.das.finished() && shift.arr.just_finished() {
if keys.pressed(KeyCode::A) {
game.translate(-1, 0);
}
if keys.pressed(KeyCode::D) {
game.translate(1, 0);
}
}
if keys.just_pressed(KeyCode::Space) {
game.hard_drop();
}
if keys.just_pressed(KeyCode::S) {
game.translate(0, -1);
}
if keys.just_pressed(KeyCode::J) {
game.rotate(Turn::Ccw);
}
if keys.just_pressed(KeyCode::K) {
game.rotate(Turn::Cw);
}
}
fn very_smart_bot(mut games: Query<&mut Game, Without<Controlled>>) {
for mut game in &mut games {
if fastrand::u8(0..120) == 1 {
game.hard_drop();
}
}
}
pub struct GameScene;
impl Plugin for GameScene {
fn build(&self, app: &mut App) {
app.add_plugins(Material2dPlugin::<BoardMaterial>::default())
.add_event::<GameEvent>()
.add_systems(OnEnter(GameState::Gaming), setup)
.add_systems(
Update,
(
manage_games,
reorder_games,
update_tiles,
update_pieces,
handle_input,
very_smart_bot,
kill_players,
)
.run_if(in_state(GameState::Gaming)),
);
}
}