/* Hurry Curry! - a game about cooking Copyright (C) 2025 Hurry Curry! Contributors 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 anyhow::Result; use clap::Parser; use hurrycurry_bot::{algos::ALGO_CONSTRUCTORS, BotAlgo, BotInput}; use hurrycurry_client_lib::{network::sync::Network, Game}; use hurrycurry_protocol::{Character, Hand, PacketC, PacketS, PlayerClass, PlayerID}; use log::warn; use std::{thread::sleep, time::Duration}; #[derive(Parser)] struct Args { /// Bot player name, algo name by default #[arg(short, long)] username: Option, /// Character color #[arg(short, long, default_value_t = 0)] character_color: i32, /// Character headwear #[arg(short, long, default_value_t = 0)] character_headwear: i32, /// Character hairstyle #[arg(short, long, default_value_t = 0)] character_hairstyle: i32, algo: String, /// Websocket address of the server address: String, } pub struct BotDriver { pub interacting: bool, id: PlayerID, state: Box, } fn main() -> Result<()> { env_logger::init_from_env("LOG"); rustls::crypto::ring::default_provider() .install_default() .unwrap(); let args = Args::parse(); let mut network = Network::connect(&args.address)?; let mut game = Game::default(); network.queue_out.push_back(PacketS::Join { name: format!("{}-bot", args.username.clone().unwrap_or(args.algo.clone())), character: Character { color: args.character_color, hairstyle: args.character_hairstyle, headwear: args.character_headwear, }, class: PlayerClass::Bot, id: None, position: None, }); let mut bots = Vec::new(); loop { let dt = 1. / 50.; network.poll()?; while let Some(packet) = network.queue_in.pop_front() { match &packet { PacketC::Joined { id } => bots.push(BotDriver { id: *id, interacting: false, state: ALGO_CONSTRUCTORS .iter() .find(|(n, _)| n == &args.algo) .map(|(_, c)| c()) .unwrap_or_else(|| panic!("unknown algo {:?}", args.algo)), }), PacketC::ServerMessage { message, error: true, } => { warn!("server error message: {message:?}"); } _ => (), } game.apply_packet(packet); } bots.retain_mut(|b| { let BotInput { direction, boost, interact, leave, extra, } = b.state.tick(b.id, &game, dt); if leave { network.queue_out.push_back(PacketS::Leave { player: b.id }); return false; } if interact.is_some() != b.interacting { b.interacting = interact.is_some(); network.queue_out.push_back(PacketS::Interact { player: b.id, pos: interact, hand: Hand(0), }) } network.queue_out.push_back(PacketS::Movement { player: b.id, dir: direction, boost, pos: None, }); network.queue_out.extend(extra); true }); sleep(Duration::from_secs_f32(dt)); } }