use std::cell::RefCell;
use anyhow::Context;
use ev3dev_lang_rust::{motors::MotorPort, sensors::SensorPort};
use fll_rs::{
graphics::{
display::{self, Display, Renderer},
menu::Menu,
},
lego::{
graphics_impl::LegoDisplay,
robot_impl::{AngleAlgorithm, LegoRobot},
},
movement::{pid::PidConfig, spec::RobotSpec},
robot::{MotorId, Robot},
types::UnitsExt,
};
fn main() -> anyhow::Result<()> {
let motor_definitions = &[
(MotorId::Attachment1, MotorPort::OutA),
(MotorId::DriveLeft, MotorPort::OutB),
(MotorId::DriveRight, MotorPort::OutC),
(MotorId::Attachment2, MotorPort::OutD),
];
let pid_config = PidConfig {
kp: 0.3,
ki: 0.02,
kd: 0.6,
max_i: 200.0,
};
let spec = RobotSpec::new(1000.dps2(), 500.dps2(), 88.mm(), 120.mm(), 1000.dps());
let angle_algorithm = AngleAlgorithm::Gyro(SensorPort::In3);
let robot = RefCell::new(
LegoRobot::new(motor_definitions, pid_config, spec, angle_algorithm)
.context("Create robot")?,
);
let display = LegoDisplay::new().context("Create display")?;
let renderer = RefCell::new(Renderer::new(display));
let mut main_menu = Menu::new(5);
let reset_attachments = wrap_mission(&robot, &renderer, missions::reset_attachments);
main_menu.push("Reset attachments", &reset_attachments);
let mission_test = wrap_mission(&robot, &renderer, missions::test);
main_menu.push("Test", &mission_test);
let snake_game = || {
games::snake(&robot, &renderer);
};
main_menu.push("Snake Game", &snake_game);
loop {
main_menu.render(&mut renderer.borrow_mut());
let new_input = robot.borrow().await_input(None).context("Await imput")?;
if !main_menu.notify_input(&new_input) {
return Ok(());
}
}
}
#[allow(clippy::precedence)]
mod missions {
use anyhow::{Context, Result};
use fll_rs::robot::{Command, MotorExt, MotorId, Robot, StopAction, TurnType};
use fll_rs::types::UnitsExt;
use std::thread;
use std::time::Duration;
pub fn test<R: Robot>(robot: &R) -> Result<()> {
let mut attachment1 = robot.motor(MotorId::Attachment1).context("Attachment 1")?;
let mut attachment2 = robot.motor(MotorId::Attachment2).context("Attachment 2")?;
attachment1.motor_reset(Some(StopAction::Hold))?;
attachment2.motor_reset(Some(StopAction::Hold))?;
robot.drive(100.deg(), 80.pct())?;
robot.turn(90.ang(), 80.pct())?;
robot.drive(100.deg(), 80.pct())?;
robot.turn(180.ang(), 80.pct())?;
robot.drive(100.deg(), 80.pct())?;
robot.turn(-90.ang(), 80.pct())?;
robot.drive(100.deg(), 80.pct())?;
robot.turn(0.ang(), 80.pct())?;
Ok(())
}
pub fn reset_attachments<R: Robot>(robot: &R) -> Result<()> {
let mut attachment1 = robot.motor(MotorId::Attachment1).context("Attachment 1")?;
let mut attachment2 = robot.motor(MotorId::Attachment2).context("Attachment 2")?;
attachment1.raw(Command::Time(
Duration::from_secs_f64(1.0),
100.pct().into(),
))?;
attachment2.raw(Command::Time(
Duration::from_secs_f64(0.5),
100.pct().into(),
))?;
attachment1.wait(None)?;
attachment2.wait(None)?;
Ok(())
}
}
mod games {
use std::{
cell::RefCell,
collections::HashSet,
thread,
time::{Duration, Instant},
};
use fll_rs::{
graphics::display::{self, Display, Renderer},
math,
robot::Robot,
};
use rand::Rng;
pub fn snake<R: Robot, D: Display>(robot: &RefCell<R>, renderer: &RefCell<Renderer<D>>) {
let board_size = (40i32, 30i32);
let apple_count = 5;
let start = Instant::now();
let robot = robot.borrow_mut();
let mut renderer = renderer.borrow_mut();
let mut random = rand::thread_rng();
let mut snake = vec![(board_size.0 / 2, board_size.1 / 2)];
let mut apples = HashSet::new();
let mut direction = (0, 0);
while apples.len() < apple_count {
apples.insert((
random.gen_range(0..board_size.0),
random.gen_range(0..board_size.1),
));
}
let mut deadline = Instant::now();
let game_loop_duration = Duration::from_millis(400);
loop {
loop {
let remaining = deadline - Instant::now();
let Ok(input) = robot.await_input(Some(remaining)) else { return; };
if input.is_up() {
direction = (0, -1);
}
if input.is_down() {
direction = (0, 1);
}
if input.is_right() {
direction = (1, 0);
}
if input.is_left() {
direction = (-1, 0);
}
if input.is_enter() && start.elapsed() > Duration::from_millis(500) {
return;
}
if remaining.is_zero() {
break;
}
}
{
let Some(head) = snake.first() else { return; };
let Some(tail) = snake.last() else { return; };
if apples.remove(head) {
while apples.len() < apple_count {
apples.insert((
random.gen_range(0..board_size.0),
random.gen_range(0..board_size.1),
));
}
snake.push(*tail);
}
}
{
let last_snake = snake.clone();
for (idx, part) in snake.iter_mut().enumerate().skip(1) {
*part = last_snake[idx - 1];
}
}
{
let Some(head) = snake.first_mut() else { return; };
head.0 += direction.0;
head.1 += direction.1;
head.0 = math::modi(head.0, board_size.0);
head.1 = math::modi(head.1, board_size.1);
}
{
let Some(head) = snake.first() else { return; };
if snake[1..].contains(head) {
renderer.clear();
renderer.draw_text("You died", 0, 0, 20.0, display::BLACK);
renderer.update();
thread::sleep(Duration::from_millis(1500));
return;
}
}
{
let pixels_per_tile_x = (renderer.width() as i32 - board_size.0) / board_size.0;
let pixels_per_tile_y = (renderer.height() as i32 - board_size.1) / board_size.1;
renderer.clear();
for (x, y) in &snake {
renderer.draw_rectangle_solid(
x * pixels_per_tile_x + x,
y * pixels_per_tile_y + y,
pixels_per_tile_x as u32,
pixels_per_tile_y as u32,
display::BLACK,
);
}
for (x, y) in &apples {
renderer.draw_rectangle_solid(
x * pixels_per_tile_x + x,
y * pixels_per_tile_y + y,
pixels_per_tile_x as u32,
pixels_per_tile_y as u32,
display::BLACK,
);
}
renderer.update();
}
{
deadline += game_loop_duration;
}
}
}
}
fn wrap_mission<'a, R: Robot, D: Display, F: Fn(&R) -> anyhow::Result<()> + 'a>(
robot: &'a RefCell<R>,
renderer: &'a RefCell<Renderer<D>>,
code: F,
) -> impl Fn() + 'a {
move || {
let robot = robot.borrow();
let rst = (|| {
robot.reset().context("Reset robot")?;
let rst = (code)(&robot).context("Mission");
robot.stop().context("Cleanup robot")?;
rst
})();
if let Err(err) = rst {
eprintln!("{err:?}");
let mut renderer = renderer.borrow_mut();
renderer.clear();
renderer.draw_text(&format!("{err}"), 0, 0, 5.0, display::BLACK);
renderer.update();
let _ = robot.await_input(None);
}
}
}