use flo_draw::*;
use flo_canvas::*;
use flo_stream::*;
use rand::*;
use futures::prelude::*;
use futures::stream;
use futures::executor;
use futures_timer::*;
use std::f64;
use std::time::*;
pub fn main() {
with_2d_graphics(|| {
executor::block_on(async {
let (canvas, events) = create_drawing_window_with_events("Vectoroids");
let tick_stream = tick_stream();
let events = events.map(|evt| VectorEvent::DrawEvent(evt));
let mut events = stream::select(events, tick_stream);
let ship_sprite = SpriteId(0);
let bullet_sprite = SpriteId(1);
let roid_sprite = SpriteId(2);
canvas.draw(|gc| {
gc.clear_canvas(Color::Rgba(0.1, 0.1, 0.1, 1.0));
gc.canvas_height(1000.0);
gc.center_region(0.0, 0.0, 1000.0, 1000.0);
gc.sprite(ship_sprite);
gc.clear_sprite();
gc.new_path();
gc.move_to(-10.0, -8.0);
gc.line_to(0.0, 12.0);
gc.line_to(10.0, -8.0);
gc.line_to(8.0, -6.0);
gc.line_to(-8.0, -6.0);
gc.line_to(-10.0, -8.0);
gc.line_width(2.0);
gc.stroke_color(Color::Rgba(0.8, 0.7, 0.0, 1.0));
gc.stroke();
gc.sprite(bullet_sprite);
gc.clear_sprite();
gc.new_path();
gc.rect(-1.0, -1.0, 1.0, 1.0);
gc.fill_color(Color::Rgba(1.0, 0.8, 0.0, 1.0));
gc.fill();
gc.sprite(roid_sprite);
gc.clear_sprite();
gc.new_path();
gc.move_to(0.0, -15.0);
gc.line_to(-15.0, -25.0);
gc.line_to(-35.0, -15.0);
gc.line_to(-20.0, 0.0);
gc.line_to(-25.0, 5.0);
gc.line_to(-10.0, 20.0);
gc.line_to(5.0, 25.0);
gc.line_to(30.0, 5.0);
gc.line_to(20.0, -15.0);
gc.line_to(10.0, -25.0);
gc.line_to(0.0, -15.0);
gc.line_width(3.0);
gc.stroke_color(Color::Rgba(0.6, 0.5, 0.0, 1.0));
gc.stroke();
gc.layer(LayerId(0));
gc.clear_layer();
gc.fill_color(Color::Rgba(0.7, 0.7, 0.8, 1.0));
for _star in 0..200 {
let x = random::<f32>() * 1000.0;
let y = random::<f32>() * 1000.0;
gc.rect(x-1.0, y-1.0, x+1.0, y+1.0);
gc.fill();
}
});
let mut game_state = GameState::new(ship_sprite, roid_sprite);
while let Some(event) = events.next().await {
match event {
VectorEvent::Tick => {
game_state.tick();
canvas.draw(|gc| {
gc.layer(LayerId(1));
gc.clear_layer();
game_state.draw(gc);
});
}
VectorEvent::DrawEvent(DrawEvent::KeyDown(_, Some(Key::KeyLeft))) => {
game_state.ship.rotation = (360.0) / 60.0;
}
VectorEvent::DrawEvent(DrawEvent::KeyDown(_, Some(Key::KeyRight))) => {
game_state.ship.rotation = -(360.0) / 60.0;
}
VectorEvent::DrawEvent(DrawEvent::KeyUp(_, Some(Key::KeyLeft))) |
VectorEvent::DrawEvent(DrawEvent::KeyUp(_, Some(Key::KeyRight))) => {
game_state.ship.rotation = 0.0;
}
VectorEvent::DrawEvent(DrawEvent::KeyDown(_, Some(Key::KeyUp))) => {
game_state.ship.thrust = 0.3;
}
VectorEvent::DrawEvent(DrawEvent::KeyUp(_, Some(Key::KeyUp))) => {
game_state.ship.thrust = 0.0;
}
VectorEvent::DrawEvent(DrawEvent::KeyDown(_, Some(Key::KeySpace))) => {
game_state.bullets.push(Bullet::new(bullet_sprite, game_state.ship.x, game_state.ship.y, game_state.ship.vel_x, game_state.ship.vel_y, game_state.ship.angle));
}
_ => { }
}
}
});
});
}
enum VectorEvent {
Tick,
DrawEvent(DrawEvent)
}
struct GameState {
ship: Ship,
roids: Vec<Roid>,
bullets: Vec<Bullet>
}
struct Ship {
sprite: SpriteId,
x: f64,
y: f64,
angle: f64,
vel_x: f64,
vel_y: f64,
rotation: f64,
thrust: f64
}
struct Roid {
sprite: SpriteId,
x: f64,
y: f64,
angle: f64,
rotation: f64,
vel_x: f64,
vel_y: f64,
}
struct Bullet {
sprite: SpriteId,
x: f64,
y: f64,
vel_x: f64,
vel_y: f64,
time_left: usize
}
impl GameState {
pub fn new(ship_sprite: SpriteId, roid_sprite: SpriteId) -> GameState {
GameState {
ship: Ship::new(ship_sprite),
roids: (0..20).into_iter().map(|_| Roid::new(roid_sprite)).collect(),
bullets: vec![]
}
}
pub fn tick(&mut self) {
self.ship.tick();
self.roids.iter_mut().for_each(|roid| roid.tick());
self.bullets.iter_mut().for_each(|bullet| bullet.tick());
self.bullets.retain(|bullet| bullet.time_left > 0);
}
pub fn draw(&self, gc: &mut dyn GraphicsContext) {
self.roids.iter().for_each(|roid| roid.draw(gc));
self.ship.draw(gc);
self.bullets.iter().for_each(|bullet| bullet.draw(gc));
}
}
impl Ship {
pub fn new(sprite: SpriteId) -> Ship {
Ship {
sprite: sprite,
x: 500.0,
y: 500.0,
vel_x: 0.0,
vel_y: 0.0,
angle: 0.0,
rotation: 0.0,
thrust: 0.0
}
}
pub fn tick(&mut self) {
self.x += self.vel_x;
self.y += self.vel_y;
self.angle += self.rotation;
self.angle = self.angle % 360.0;
if self.x < 0.0 { self.x = 1000.0 };
if self.y < 0.0 { self.y = 1000.0 };
if self.x > 1000.0 { self.x = 0.0 };
if self.y > 1000.0 { self.y = 0.0 };
let (acc_x, acc_y) = Transform2D::rotate_degrees(self.angle as _).transform_point(0.0, self.thrust as _);
self.vel_x += acc_x as f64;
self.vel_y += acc_y as f64;
self.vel_x *= 0.99;
self.vel_y *= 0.99;
}
pub fn draw(&self, gc: &mut dyn GraphicsContext) {
gc.sprite_transform(SpriteTransform::Identity);
gc.sprite_transform(SpriteTransform::Translate(self.x as _, self.y as _));
gc.sprite_transform(SpriteTransform::Rotate(self.angle as _));
gc.draw_sprite(self.sprite);
}
}
impl Roid {
pub fn new(sprite: SpriteId) -> Roid {
Roid {
sprite: sprite,
x: random::<f64>() * 1000.0,
y: random::<f64>() * 1000.0,
vel_x: random::<f64>() * 3.0 - 1.5,
vel_y: random::<f64>() * 3.0 - 1.5,
angle: random::<f64>() * 360.0,
rotation: random::<f64>() * 8.0 - 4.0
}
}
pub fn tick(&mut self) {
self.x += self.vel_x;
self.y += self.vel_y;
self.angle += self.rotation;
self.angle = self.angle % 360.0;
if self.x < 0.0 { self.x = 1000.0 };
if self.y < 0.0 { self.y = 1000.0 };
if self.x > 1000.0 { self.x = 0.0 };
if self.y > 1000.0 { self.y = 0.0 };
}
pub fn draw(&self, gc: &mut dyn GraphicsContext) {
gc.sprite_transform(SpriteTransform::Identity);
gc.sprite_transform(SpriteTransform::Translate(self.x as _, self.y as _));
gc.sprite_transform(SpriteTransform::Rotate(self.angle as _));
gc.draw_sprite(self.sprite);
}
}
impl Bullet {
pub fn new(sprite: SpriteId, x: f64, y: f64, ship_vel_x: f64, ship_vel_y: f64, ship_angle: f64) -> Bullet {
let transform = Transform2D::rotate_degrees(ship_angle as _);
let (offset_x, offset_y) = transform.transform_point(0.0, 11.0);
let (x, y) = (x+offset_x as f64, y+offset_y as f64);
let (vel_x, vel_y) = transform.transform_point(0.0, 4.0);
let (vel_x, vel_y) = (ship_vel_x+vel_x as f64, ship_vel_y+vel_y as f64);
Bullet {
sprite: sprite,
x: x,
y: y,
vel_x: vel_x,
vel_y: vel_y,
time_left: 90
}
}
pub fn tick(&mut self) {
self.x += self.vel_x;
self.y += self.vel_y;
self.time_left -= 1;
if self.x < 0.0 { self.x = 1000.0 };
if self.y < 0.0 { self.y = 1000.0 };
if self.x > 1000.0 { self.x = 0.0 };
if self.y > 1000.0 { self.y = 0.0 };
}
pub fn draw(&self, gc: &mut dyn GraphicsContext) {
gc.sprite_transform(SpriteTransform::Identity);
gc.sprite_transform(SpriteTransform::Translate(self.x as _, self.y as _));
gc.draw_sprite(self.sprite);
}
}
fn tick_stream() -> impl Send+Unpin+Stream<Item=VectorEvent> {
generator_stream(|yield_value| async move {
let start_time = Instant::now();
let mut last_time = Duration::from_millis(0);
let max_ticks_per_call = 5;
let tick_length = Duration::from_nanos(1_000_000_000 / 60);
loop {
let elapsed = start_time.elapsed() - last_time;
let mut remaining = elapsed;
let mut num_ticks = 0;
while remaining >= tick_length {
if num_ticks < max_ticks_per_call {
yield_value(VectorEvent::Tick).await;
num_ticks += 1;
}
remaining -= tick_length;
last_time += tick_length;
}
let next_time = tick_length - remaining;
let wait_time = Duration::min(tick_length / 2, next_time);
Delay::new(wait_time).await;
}
}.boxed())
}