diff --git a/client/client.lua b/client/client.lua index 18d2db1..5a99677 100644 --- a/client/client.lua +++ b/client/client.lua @@ -1,3 +1,19 @@ +local function refuel() + turtle.select(16) + turtle.dropUp() + while turtle.getFuelLevel() ~= turtle.getFuelLimit() do + turtle.suck() + turtle.refuel() + end +end + +local function dump() + for i = 1, 16, 1 do + turtle.select(i) + turtle.drop() + end +end + local port = "48228" local endpoint = "http://" .. ipaddr .. ":" .. port @@ -120,6 +136,10 @@ repeat ret = turtle.digDown() elseif command == "ItemInfo" then ret = { Item = turtle.getItemDetail(args) } + elseif command == "Refuel" then + refuel() + elseif command == "Dump" then + dump() elseif command == "Update" then if not update({...}) then break diff --git a/server/Cargo.toml b/server/Cargo.toml index 81b51be..f14e2a2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,14 +10,16 @@ anyhow = "1.0.75" axum = "0.7.2" bit-struct = "0.3.2" const_format = "0.2.32" +erased-serde = "0.4.1" feistel_rs = "0.1.0" +hilbert_index = "0.2.0" hyper = "1.0.1" hyper-util = "0.1.1" nalgebra = { version = "0.32.3", features = ["serde-serialize"] } pathfinding = "4.6.0" rstar = { version = "0.11.0", features = ["serde"] } rustmatica = "0.1.1" -serde = "1.0.193" +serde = { version = "1.0.193", features = ["rc"] } serde_json = "1.0.108" tokio = { version = "1.0", features = ["full"] } tower = { version = "0.4", features = ["util", "timeout", "load-shed", "limit"] } @@ -31,3 +33,4 @@ tower-http = { version = "0.5.0", features = [ tower-layer = "0.3.2" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +typetag = "0.2.14" diff --git a/server/src/blocks.rs b/server/src/blocks.rs index 53f1877..25cfc3d 100644 --- a/server/src/blocks.rs +++ b/server/src/blocks.rs @@ -62,3 +62,26 @@ impl Direction { } } } + +/// closest valid state to the given point from where you are +pub fn nearest(from: Vec3, to: Vec3) -> Position { + let diff = to.xz()-from.xz(); + + let dir = if diff.x.abs() > diff.y.abs() { + if diff.x > 0 { + Direction::East + } else { + Direction::West + } + } else { + if diff.y > 0 { + Direction::South + } else { + Direction::South + } + }; + ( + to - dir.unit(), + dir + ) +} diff --git a/server/src/main.rs b/server/src/main.rs index a3e3510..9de8521 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -8,6 +8,7 @@ use axum::{ Json, Router, }; use blocks::{World, Position}; +use mine::TurtleMineJob; use rstar::{self, AABB}; use const_format::formatcp; @@ -20,6 +21,7 @@ use tokio::sync::{ Mutex, RwLock, }; use tower::Service; +use turtle::TurtleTask; use crate::{blocks::Block, paths::route}; @@ -113,7 +115,9 @@ async fn set_goal( State(state): State, Json(req): Json, ) -> &'static str { - state.write().await.turtles[id as usize].goal = Some(req); + state.write().await.turtles[id as usize].add_task( + TurtleMineJob::chunk(req.0) + ); "ACK" } @@ -135,11 +139,7 @@ async fn turtle_info( let state = &mut state.read().await; let turtle = &state.turtles[id as usize]; - let mut pseudomoves: VecDeque = VecDeque::new(); - turtle - .moves - .front() - .map(|m| pseudomoves.push_front(m.clone())); + let tasks: VecDeque> = VecDeque::new(); let cloned = turtle::Turtle { name: turtle.name.clone(), @@ -148,7 +148,7 @@ async fn turtle_info( position: turtle.position.clone(), goal: turtle.goal.clone(), pending_update: turtle.pending_update, - moves: pseudomoves, + tasks, }; Json(cloned) diff --git a/server/src/turtle.rs b/server/src/turtle.rs index fe33f60..46bdc8e 100644 --- a/server/src/turtle.rs +++ b/server/src/turtle.rs @@ -3,16 +3,19 @@ use crate::blocks::Block; use crate::blocks::Direction; use crate::blocks::Position; use crate::blocks::Vec3; +use crate::blocks::nearest; use crate::mine::TurtleMineJob; use anyhow::Ok; use anyhow; use anyhow::Context; +use tokio::sync::RwLock; use super::ControlState; use std::collections::VecDeque; +use std::sync::Arc; use super::names::Name; @@ -29,9 +32,24 @@ pub(crate) struct Turtle { /// movement vector of last given command pub(crate) queued_movement: Vec3, pub(crate) position: Position, - pub(crate) goal: Option, + pub(crate) goal: Option, pub(crate) pending_update: bool, - pub(crate) moves: VecDeque, + #[serde(skip)] + pub(crate) tasks: VecDeque>>, +} + +impl Default for Turtle { + fn default() -> Self { + Self { + name: Name::from_num(0), + fuel: Default::default(), + queued_movement: Default::default(), + position: (Vec3::zeros(), Direction::North), + goal: Default::default(), + pending_update: Default::default(), + tasks: VecDeque::new(), + } + } } impl Turtle { @@ -43,9 +61,13 @@ impl Turtle { position: (position, facing), goal: None, pending_update: true, - moves: VecDeque::new(), + tasks: VecDeque::new(), } } + + pub fn add_task(&mut self, task: impl TurtleTask + Send + Sync) { + self.tasks.push_back(Arc::new(task)); + } } pub(crate) fn process_turtle_update( @@ -64,10 +86,6 @@ pub(crate) fn process_turtle_update( return Ok(TurtleCommand::Update); } - println!( - "above: {}, below: {}, ahead: {}", - update.above, update.below, update.ahead - ); if turtle.fuel != update.fuel { turtle.fuel = update.fuel; @@ -96,45 +114,66 @@ pub(crate) fn process_turtle_update( world.remove_at_point(&below.pos.into()); world.insert(below); - if turtle.goal.is_some_and(|g| g == turtle.position) { - turtle.goal = None; + if let Some(task) = turtle.tasks.front() { + task.handle_block(above); + task.handle_block(below); + task.handle_block(ahead); } - if let Some(goal) = turtle.goal { - // TODO: memoize this whenever we aren't digging - let route = route(turtle.position, goal, world).unwrap(); - println!("route: {:?}", route); - let mut next_move = difference(route[0], route[1]).unwrap(); - if world - .locate_at_point(&route[1].0.into()) - .is_some_and(|b| b.name != "minecraft:air") - { - next_move = match next_move { - TurtleCommand::Up(_) => TurtleCommand::DigUp, - TurtleCommand::Down(_) => TurtleCommand::DigDown, - TurtleCommand::Forward(_) => TurtleCommand::Dig, - _ => next_move, - } - } - turtle.queued_movement = next_move.delta(turtle.position.1); - match next_move { - TurtleCommand::Left => turtle.position.1 = turtle.position.1.left(), - TurtleCommand::Right => turtle.position.1 = turtle.position.1.right(), - _ => {} - } - return Ok(next_move); - } + if let Some(goal) = turtle.tasks.front().map(|t| t.next(&turtle)) { + let command = match goal { + Iota::End => { + turtle.tasks.pop_front(); + TurtleCommand::Wait(0) // TODO: fix + }, + Iota::Goto(pos) => { + pathstep(turtle, world, pos).unwrap() + }, + Iota::Mine(pos) => { + let pos = nearest(turtle.position.0, pos); + + if pos == turtle.position { + TurtleCommand::Dig + } else { + pathstep(turtle, world, pos).unwrap() + } + }, + Iota::Execute(cmd) => { + cmd + }, + }; + + return Ok(command); + }; Ok(TurtleCommand::Wait(3)) } -#[derive(Serialize, Deserialize)] -pub(crate) enum TurtleTask { - Mining(TurtleMineJob), - Idle, +fn pathstep(turtle: &mut Turtle, world: &mut rstar::RTree, goal: Position) -> Option { + // TODO: memoize this whenever we aren't digging + let route = route(turtle.position, goal, world)?; + let mut next_move = difference(route[0], route[1])?; + if world + .locate_at_point(&route[1].0.into()) + .is_some_and(|b| b.name != "minecraft:air") + { + next_move = match next_move { + TurtleCommand::Up(_) => TurtleCommand::DigUp, + TurtleCommand::Down(_) => TurtleCommand::DigDown, + TurtleCommand::Forward(_) => TurtleCommand::Dig, + _ => next_move, + } + } + turtle.queued_movement = next_move.delta(turtle.position.1); + match next_move { + TurtleCommand::Left => turtle.position.1 = turtle.position.1.left(), + TurtleCommand::Right => turtle.position.1 = turtle.position.1.right(), + _ => {} + } + return Some(next_move); } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub(crate) enum TurtleCommand { Wait(u32), Forward(u32), @@ -158,6 +197,8 @@ pub(crate) enum TurtleCommand { ItemInfo(u32), Update, Poweroff, + Refuel, + Dump, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -240,3 +281,19 @@ fn difference(from: Position, to: Position) -> Option { None } } + +#[derive(Serialize, Deserialize, Clone)] +pub enum Iota { + End, + Goto(Position), + Mine(Vec3), + Execute(TurtleCommand), +} + +pub trait TurtleTask: erased_serde::Serialize { + fn handle_block(&mut self, _: Block) { } + fn next(&mut self, turtle: &Turtle) -> Iota + { Iota::End } +} + +erased_serde::serialize_trait_object!(TurtleTask);