mnml/server/src/zone.rs
2018-12-30 15:15:34 +11:00

253 lines
6.1 KiB
Rust

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<Encounter, ()>,
}
pub type ZoneGraph = UnGraph<Encounter, ()>;
#[derive(Debug,Clone,PartialEq,Eq,Hash,PartialOrd,Ord,Serialize,Deserialize)]
pub struct Encounter {
tag: String,
game_id: Option<Uuid>,
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<Zone, Error> {
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<u8> = returned.get("data");
let zone = match from_slice::<Zone>(&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<Zone, Error> {
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<Game, Error> {
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::<Vec<NodeIndex>>();
// 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)));
}
}