feat: Added Map

- Implemented Map
- Partially implemented CLI interaction with map
- Added load_map test
This commit is contained in:
Alexey 2025-12-03 17:01:40 +03:00
commit b9f75e426c
6 changed files with 371 additions and 25 deletions

View file

@ -1,8 +1,8 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use clap::{Parser,Subcommand,Args,ValueEnum};
use serde::Deserialize;
use squad_quest::{SquadObject, account::Account, config::Config, quest::{Quest,QuestDifficulty as LibQuestDifficulty}};
use squad_quest::{SquadObject, account::Account, config::Config, error::Error, map::{Map, Room}, quest::{Quest,QuestDifficulty as LibQuestDifficulty}};
use toml::value::Date;
use chrono::{Datelike, NaiveDate, Utc};
@ -37,6 +37,9 @@ enum Objects {
/// Operations on the accounts
#[command(subcommand)]
Account(AccountCommands),
/// Operations on the map rooms
#[command(subcommand)]
Map(MapCommands),
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
@ -208,6 +211,62 @@ struct AccountDeleteArgs {
id: String,
}
#[derive(Subcommand)]
enum MapCommands {
/// List all rooms with connections
List,
/// Add new room to map
Add(MapAddArgs),
/// Connect two rooms
Connect(MapConnectArgs),
/// Disconnect two rooms if they're connected
Disconnect(MapConnectArgs),
/// Remove all connections with the room
Delete(MapDeleteArgs),
/// Update room data
Update(MapUpdateArgs),
}
#[derive(Args)]
struct MapAddArgs {
/// Name of the room
name: String,
/// Price of the room
value: u32,
/// Optional description for the room
#[arg(long,short)]
description: Option<String>,
}
#[derive(Args)]
struct MapConnectArgs {
/// First room ID
first: u16,
/// Second room ID
second: u16,
}
#[derive(Args)]
struct MapDeleteArgs {
/// ID of the room to delete
id: u16,
}
#[derive(Args)]
struct MapUpdateArgs {
/// ID of the room to update
id: u16,
/// Room name
#[arg(short,long)]
name: Option<String>,
/// Room description
#[arg(short,long)]
description: Option<String>,
/// Room price
#[arg(short,long)]
value: Option<u32>,
}
fn print_quest_short(quest: &Quest) {
println!("Quest #{}: {}", quest.id, quest.name);
}
@ -219,7 +278,7 @@ fn print_quest_long(quest: &Quest) {
println!("Answer:\n{}", quest.answer);
}
fn main() {
fn main() -> Result<(), Error> {
let cli = Cli::parse();
let config = Config::load(cli.config.clone());
@ -241,9 +300,6 @@ fn main() {
let mut quests = config.load_quests();
quests.sort_by(|a,b| a.id.cmp(&b.id));
let next_id = match quests.last() {
Some(quest) if quest.id == u16::MAX => {
panic!("Error: quest list contains quest with u16::MAX id.");
},
Some(quest) => quest.id + 1u16,
None => 0u16
};
@ -342,8 +398,7 @@ fn main() {
let not_str = if args.reverse {" not "} else {" "};
if quest.public != args.reverse {
println!("Quest #{} is already{}public", quest.id, not_str);
return;
panic!("Quest #{} is already{}public", quest.id, not_str);
}
quest.public = !args.reverse;
@ -358,8 +413,8 @@ fn main() {
},
}
},
Objects::Account(args) => {
match args {
Objects::Account(commands) => {
match commands {
AccountCommands::List => {
let accounts = config.load_accounts();
@ -376,8 +431,7 @@ fn main() {
let accounts = config.load_accounts();
if let Some(_) = accounts.iter().find(|a| a.id == account.id) {
eprintln!("Error: account {} exists.", account.id);
return;
panic!("Error: account {} exists.", account.id);
}
let accounts_path = config.full_accounts_path();
@ -397,8 +451,7 @@ fn main() {
let account = match accounts.iter_mut().find(|a| a.id == args.id) {
Some(acc) => acc,
None => {
eprintln!("Could not find account \"{}\"", args.id);
return;
panic!("Could not find account \"{}\"", args.id);
}
};
@ -414,8 +467,7 @@ fn main() {
if args.negative_ok {
account.balance = 0u32;
} else {
eprintln!("Error: balance ({}) is less than {}.", account.balance, args.value);
return;
panic!("Error: balance ({}) is less than {}.", account.balance, args.value);
}
} else {
account.balance -= args.value;
@ -440,16 +492,14 @@ fn main() {
let account = match accounts.iter_mut().find(|a| a.id == args.account) {
Some(acc) => acc,
None => {
eprintln!("Could not find account \"{}\"", args.account);
return;
panic!("Could not find account \"{}\"", args.account);
}
};
let quests = config.load_quests();
if let None = quests.iter().find(|q| q.id == args.quest) {
eprintln!("Could not find quest #{}", args.quest);
return;
panic!("Could not find quest #{}", args.quest);
}
match account.quests_completed.iter().find(|qid| **qid == args.quest) {
@ -483,6 +533,101 @@ fn main() {
}
},
}
},
Objects::Map(commands) => {
let map_path = config.full_map_path();
let mut map = Map::load(map_path.clone())?;
map.room.sort_by(|a,b| a.id.cmp(&b.id));
match commands {
MapCommands::List => {
for room in map.room {
println!("Room #{}: {}; Connections: {:?}", room.id, room.name, room.children);
}
},
MapCommands::Add(args) => {
let last_id = match map.room.last() {
Some(r) => r.id + 1u16,
None => 0u16
};
let room = Room {
id: last_id,
name: args.name.clone(),
description: args.description.clone(),
..Default::default()
};
let r_id = room.id;
map.room.push(room);
match map.save(map_path.parent().unwrap_or(Path::new("")).to_owned()) {
Ok(_) => {
println!("Created room #{}.", r_id);
println!("Successfully saved map.");
},
Err(error) => {
eprintln!("Error while saving map: {error}");
}
}
},
MapCommands::Delete(args) => {
let Some(room) = map.room.iter().find(|r| r.id == args.id) else {
panic!("Error: Room #{} not found", args.id);
};
let r_id = room.id;
let index = map.room.iter().position(|r| r.eq(room)).unwrap();
map.room.remove(index);
for room in map.room.iter_mut().filter(|r| r.children.contains(&r_id)) {
let idx = room.children.iter()
.position(|id| *id == r_id)
.unwrap();
room.children.remove(idx);
}
match map.save(map_path.parent().unwrap_or(Path::new("")).to_owned()) {
Ok(_) => {
println!("Removed room #{}.", r_id);
println!("Successfully saved map.");
},
Err(error) => {
eprintln!("Error while saving map: {error}");
}
}
},
MapCommands::Update(args) => {
let Some(room) = map.room.iter_mut().find(|r| r.id == args.id) else {
panic!("Error: Room #{} not found", args.id);
};
if let Some(name) = &args.name {
room.name = name.to_string();
}
if args.description.is_some() {
room.description = args.description.clone();
}
if let Some(value) = args.value {
room.value = value;
}
match map.save(map_path.parent().unwrap_or(Path::new("")).to_owned()) {
Ok(_) => {
println!("Updated room #{}.", args.id);
println!("Successfully saved map.");
},
Err(error) => {
eprintln!("Error while saving map: {error}");
}
}
},
MapCommands::Connect(_) => {
todo!();
},
MapCommands::Disconnect(_) => {
todo!();
}
}
}
}
Ok(())
}