use uuid::Uuid; use petgraph::graph::{Graph, UnGraph, NodeIndex}; // use petgraph::dot::{Dot, Config}; // Db Commons use account::Account; use serde_cbor::{from_slice, to_vec}; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; use game::{Game, PveMode, game_pve_new, game_write}; use rpc::{ZoneJoinParams, ZoneCloseParams}; #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Zone { id: Uuid, active: bool, graph: UnGraph, } pub type ZoneGraph = UnGraph; #[derive(Debug,Clone,PartialEq,Eq,Hash,PartialOrd,Ord,Serialize,Deserialize)] pub struct Encounter { tag: String, game_id: Option, success: bool, } impl Encounter { fn new(tag: &'static str) -> Encounter { return Encounter { tag: tag.to_string(), success: false, game_id: None, }; } fn start() -> Encounter { return Encounter { tag: "START".to_string(), success: true, game_id: None, }; } } pub fn zone_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { let query = " DELETE FROM zones WHERE id = $1; "; let result = tx .execute(query, &[&id])?; if result != 1 { return Err(format_err!("unable to delete zone {:?}", id)); } println!("zone deleted {:?}", id); return Ok(()); } pub fn zone_get(tx: &mut Transaction, id: Uuid) -> Result { let query = " SELECT * FROM zones WHERE id = $1 "; let result = tx .query(query, &[&id])?; let returned = match result.iter().next() { Some(row) => row, None => return Err(err_msg("zone not found")), }; // tells from_slice to cast into a cryp let bytes: Vec = returned.get("data"); let zone = match from_slice::(&bytes) { Ok(z) => z, Err(_) => { zone_delete(tx, id)?; return Err(err_msg("invalid zone removed")) }, }; return Ok(zone); } pub fn zone_create(tx: &mut Transaction, account: &Account) -> Result { let id = Uuid::new_v4(); let graph = create_zone_graph(); let zone = Zone { id, graph, active: true, }; let bytes = to_vec(&zone)?; let query = " INSERT INTO zones (id, data, account) VALUES ($1, $2, $3) RETURNING id; "; let result = tx .query(query, &[&id, &bytes, &account.id])?; result.iter().next().ok_or(format_err!("no zone written"))?; return Ok(zone); } pub fn zone_update(zone: &Zone, tx: &mut Transaction) -> Result<(), Error> { let bytes = to_vec(&zone)?; let query = " UPDATE zones SET data = $1, active = $2 WHERE id = $3 RETURNING id, data; "; let result = tx .query(query, &[&bytes, &zone.active, &zone.id])?; result.iter().next().ok_or(format_err!("zone {:?} could not be written", zone))?; return Ok(()); } pub fn zone_join(params: ZoneJoinParams, tx: &mut Transaction, account: &Account) -> Result { let mut zone = zone_get(tx, params.zone_id)?; if !node_joinable(&zone.graph, NodeIndex::from(params.node_id)) { return Err(err_msg("not not joinable")); } let mut game = game_pve_new(params.cryp_ids, PveMode::Normal, tx, account)?; game.set_zone(zone.id, params.node_id); // borrow zone to update the encounter { let node_index = NodeIndex::from(params.node_id); let encounter = zone.graph .node_weight_mut(node_index) .ok_or(err_msg("invalid encounter id"))?; encounter.game_id = Some(game.id); } // persist game_write(&game, tx)?; zone_update(&zone, tx)?; return Ok(game); } pub fn zone_close(params: ZoneCloseParams, tx: &mut Transaction, _account: &Account) -> Result<(), Error> { let mut zone = zone_get(tx, params.zone_id)?; zone.active = false; zone_update(&zone, tx)?; return Ok(()); } pub fn create_zone_graph() -> ZoneGraph { let mut gr = Graph::new_undirected(); let mut last = gr.add_node(Encounter::start()); let mut next; next = gr.add_node(Encounter::new("ZONE0")); gr.add_edge(last, next, ()); last = next; next = gr.add_node(Encounter::new("ZONE1")); gr.add_edge(last, next, ()); last = next; next = gr.add_node(Encounter::new("ZONE2")); gr.add_edge(last, next, ()); last = next; next = gr.add_node(Encounter::new("BOSS")); gr.add_edge(last, next, ()); // last = next; return gr; } pub fn node_joinable(graph: &ZoneGraph, target_index: NodeIndex) -> bool { // early return for already attempted { let target_encounter = match graph.node_weight(target_index) { Some(encounter) => encounter, None => panic!("{:?} has no weight for {:?}", graph, target_index), }; if target_encounter.game_id.is_some() { return false; } } let success_indices = graph.node_indices().filter(|i| { match graph.node_weight(*i) { Some(encounter) => encounter.success, None => panic!("no weight for {:?}", i), } }); // if a node is a neighbour of that graph // and hasn't been attempted // it is joinable for i in success_indices { match graph.neighbors(i).find(|n| *n == target_index) { Some(_n) => return true, None => continue, }; } return false; } #[cfg(test)] mod tests { use zone::*; #[test] fn create_zone_test() { let _graph = create_zone_graph(); // good shit; // let nodes = graph.node_indices().collect::>(); // println!("{:?}", nodes[0]); // println!("{:?}", graph.node_weight(nodes[0])); // println!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel])); } #[test] fn zone_joinable_test() { let graph = create_zone_graph(); assert!(node_joinable(&graph, NodeIndex::from(1))); assert!(!node_joinable(&graph, NodeIndex::from(2))); } }