From 52d99b16534631e293a23ddbc18c4ea70b71392f Mon Sep 17 00:00:00 2001 From: metamuffin Date: Sun, 11 Aug 2024 13:35:15 +0200 Subject: add recipes back to protocol --- server/bot/src/main.rs | 134 +++++++++++++++++++++++++++++++++++++++--- server/bot/src/pathfinding.rs | 96 ++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 7 deletions(-) create mode 100644 server/bot/src/pathfinding.rs (limited to 'server/bot/src') diff --git a/server/bot/src/main.rs b/server/bot/src/main.rs index 18c4617a..864141b0 100644 --- a/server/bot/src/main.rs +++ b/server/bot/src/main.rs @@ -15,13 +15,21 @@ along with this program. If not, see . */ +#![feature(isqrt)] +pub mod pathfinding; use anyhow::Result; use hurrycurry_client_lib::{network::sync::Network, Game}; -use hurrycurry_protocol::{glam::Vec2, PacketC, PacketS, PlayerID}; +use hurrycurry_protocol::{ + glam::{IVec2, Vec2}, + ItemIndex, Message, PacketC, PacketS, PlayerID, RecipeIndex, +}; +use log::{info, warn}; +use pathfinding::{find_path, Path}; use std::{thread::sleep, time::Duration}; fn main() -> Result<()> { + env_logger::init_from_env("LOG"); let mut network = Network::connect("ws://127.0.0.1")?; let mut game = Game::default(); @@ -46,11 +54,19 @@ fn main() -> Result<()> { game.apply_packet(packet); } - for b in &bots { + for b in &mut bots { + let (dir, boost, interact) = b.tick(&game); + if interact.is_some() != b.interacting { + b.interacting = interact.is_some(); + network.queue_out.push_back(PacketS::Interact { + player: b.id, + pos: interact, + }) + } network.queue_out.push_back(PacketS::Movement { player: b.id, - dir: Vec2::ONE, - boost: true, + dir, + boost, pos: None, }); } @@ -60,14 +76,118 @@ fn main() -> Result<()> { } pub struct Bot { + pub interacting: bool, + id: PlayerID, + want: Option, + take: Option, + put: Option, + path: Option, } impl Bot { pub fn new(id: PlayerID) -> Self { - Self { id } + Self { + id, + want: None, + path: None, + take: None, + put: None, + interacting: false, + } } - pub fn tick(&self, game: &Game) { - if let Some(player) = game.players.get(&self.id) {} + pub fn tick(&mut self, game: &Game) -> (Vec2, bool, Option) { + if let Some(player) = game.players.get(&self.id) { + let pos = player.movement.position; + + if let Some(path) = &mut self.path { + let dir = path.next_direction(pos); + if path.is_done() { + self.path = None; + } + return (dir, false, None); + } + if let Some(interact) = self.take.take() { + return (Vec2::ZERO, false, Some(interact)); + } + if let Some(item) = &player.item { + if Some(item.kind) == self.want { + if let Some(interact) = self.put.take() { + return (Vec2::ZERO, false, Some(interact)); + } + } + } + + if let Some(item) = self.want { + if let Some((path, target)) = find_item_on_map(game, pos.as_ivec2(), item) { + info!("target={target}"); + info!("path found"); + self.path = Some(path); + self.take = Some(target); + } else if let Some(recipe) = find_item_as_recipe_output(game, item) { + info!("recipe={recipe:?}"); + self.want = game.data.recipes[recipe.0].outputs().first().copied(); + info!("want={:?}", self.want) + } else { + warn!("stuck"); + } + } else { + if let Some((item, dest)) = select_demand(game) { + info!("want={item:?}"); + self.want = Some(item); + self.put = Some(dest); + } + } + } + (Vec2::ZERO, false, None) } } + +fn find_item_as_recipe_output(game: &Game, item: ItemIndex) -> Option { + game.data + .recipes + .iter() + .enumerate() + .find(|(_, r)| r.inputs().contains(&item)) + .map(|r| RecipeIndex(r.0)) +} + +fn find_item_on_map(game: &Game, player: IVec2, item: ItemIndex) -> Option<(Path, IVec2)> { + game.tiles.iter().find_map(|(pos, tile)| { + if let Some(i) = &tile.item { + if i.kind == item { + for xo in -1..=1 { + for yo in -1..=1 { + let t = *pos + IVec2::new(xo, yo); + if let Some(path) = find_path(&game.walkable, player, t) { + return Some((path, *pos)); + } + } + } + } + } + None + }) +} + +fn select_demand(game: &Game) -> Option<(ItemIndex, IVec2)> { + game.players + .iter() + .find_map(|(_, pl)| match &pl.communicate_persist { + Some(Message::Item(item)) => { + let pos = pl.movement.position.as_ivec2(); + for xo in -1..=1 { + for yo in -1..=1 { + let t = pos + IVec2::new(xo, yo); + if let Some(tile) = game.tiles.get(&t) { + if game.data.tile_interact[tile.kind.0] { + return Some((*item, t)); + } + } + } + } + None + } + _ => None, + }) +} diff --git a/server/bot/src/pathfinding.rs b/server/bot/src/pathfinding.rs new file mode 100644 index 00000000..87ccf391 --- /dev/null +++ b/server/bot/src/pathfinding.rs @@ -0,0 +1,96 @@ +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ +use hurrycurry_protocol::glam::{IVec2, Vec2}; +use log::trace; +use std::{ + cmp::Ordering, + collections::{BinaryHeap, HashMap, HashSet}, +}; + +#[derive(Debug, Clone)] +pub struct Path(Vec); + +impl Path { + pub fn next_direction(&mut self, position: Vec2) -> Vec2 { + if let Some(next) = self.0.last().copied() { + trace!("next {next}"); + if next.distance(position) < if self.0.len() == 1 { 0.1 } else { 0.6 } { + self.0.pop(); + } + (next - position).normalize_or_zero() * 0.5 + } else { + Vec2::ZERO + } + } + pub fn is_done(&self) -> bool { + self.0.is_empty() + } +} + +pub fn find_path(walkable: &HashSet, from: IVec2, to: IVec2) -> Option { + #[derive(Debug, PartialEq, Eq)] + struct Open(i32, IVec2, IVec2, i32); + impl PartialOrd for Open { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.cmp(&other.0)) + } + } + impl Ord for Open { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } + } + + let mut visited = HashMap::new(); + let mut open = BinaryHeap::new(); + open.push(Open(1, from, from, 0)); + + loop { + let Open(_, pos, f, distance) = open.pop()?; + if visited.contains_key(&pos) { + continue; + } + visited.insert(pos, f); + if pos == to { + break; + } + for dir in [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] { + let next = pos + dir; + if walkable.contains(&next) { + open.push(Open( + -(distance + next.distance_squared(to).isqrt()), + next, + pos, + distance + 1, + )); + } + } + } + + let mut path = Vec::new(); + let mut c = to; + loop { + path.push(c.as_vec2() + 0.5); + let cn = visited[&c]; + if cn == c { + break; + } + c = cn + } + Some(Path(path)) +} -- cgit v1.2.3-70-g09d2