use std::convert::TryFrom; use uuid::Uuid; // use rand::prelude::*; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; use account; use account::Account; use construct::{Construct, construct_select, construct_write, construct_spawn}; use names::{name as generate_name}; use img; pub const FREE_MTX: [MtxVariant; 1] = [ MtxVariant::Rename, ]; pub const SHOP_LISTINGS: [Listing; 2] = [ Listing { variant: MtxVariant::Molecular, credits: 10 }, Listing { variant: MtxVariant::Invader, credits: 10 }, ]; const NEW_IMAGE_COST: i64 = 1; const NEW_NAME_COST: i64 = 5; const NEW_CONSTRUCT_COST: i64 = 50; #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] pub struct Shop { owned: Vec, available: Vec, } #[derive(Debug,Copy,Clone,PartialEq,Serialize,Deserialize)] pub struct Listing { variant: MtxVariant, credits: u16, } #[derive(Debug,Copy,Clone,PartialEq,Serialize,Deserialize)] pub enum MtxVariant { Rename, Molecular, Invader, } impl MtxVariant { fn to_sql(&self) -> String { format!("{:?}", *self) } } // could use postgres-derive // but enums in pg are a bit of a pain impl TryFrom for MtxVariant { type Error = Error; fn try_from(v: String) -> Result { match v.as_ref() { "Rename" => Ok(MtxVariant::Rename), "Molecular" => Ok(MtxVariant::Molecular), "Invader" => Ok(MtxVariant::Invader), _ => Err(format_err!("mtx variant not found variant={:?}", v)), } } } #[derive(Debug,Copy,Clone,Serialize,Deserialize)] pub struct Mtx { id: Uuid, account: Uuid, variant: MtxVariant, } impl Mtx { pub fn new(variant: MtxVariant, account: Uuid) -> Mtx { match variant { _ => Mtx { id: Uuid::new_v4(), account, variant }, // MtxVariant::Invader => Mtx { id: Uuid::new_v4(), account, variant: self }, // MtxVariant::Molecular => Mtx { id: Uuid::new_v4(), account, variant: self }, } } pub fn delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { let query = " DELETE FROM mtx WHERE id = $1; "; let result = tx .execute(query, &[&id])?; if result != 1 { return Err(format_err!("unable to delete mtx {:?}", id)); } info!("mtx deleted {:?}", id); return Ok(()); } pub fn insert(&self, tx: &mut Transaction) -> Result<&Mtx, Error> { let query = " INSERT INTO mtx (id, account, variant) VALUES ($1, $2, $3) RETURNING id, account; "; let result = tx .query(query, &[&self.id, &self.account, &self.variant.to_sql()])?; result.iter().next().ok_or(err_msg("mtx not written"))?; info!("wrote mtx {:?}", self); return Ok(self); } // pub fn update(&self, tx: &mut Transaction) -> Result<&Mtx, Error> { // let query = " // UPDATE mtx // SET data = $1, updated_at = now() // WHERE id = $2 // RETURNING id, data; // "; // let result = tx // .query(query, &[&self.id, &to_vec(self)?])?; // if let None = result.iter().next() { // return Err(err_msg("mtx not written")); // } // info!("wrote mtx {:?}", self); // return Ok(self); // } } pub fn apply(tx: &mut Transaction, account: &Account, variant: MtxVariant, construct_id: Uuid, name: String) -> Result, Error> { let mtx = select(tx, variant, account.id)?; let mut construct = construct_select(tx, construct_id, account.id)?; let cost = match mtx.variant { MtxVariant::Rename => NEW_NAME_COST, _ => NEW_IMAGE_COST, }; account::debit(tx, account.id, cost)?; construct = match mtx.variant { MtxVariant::Rename => construct.new_name(name), _ => construct.new_img(), }; match mtx.variant { MtxVariant::Invader => img::invader_write(construct.img)?, MtxVariant::Molecular => img::molecular_write(construct.img)?, _ => construct.img, }; construct_write(tx, construct)?; account::team(tx, account) } pub fn select(tx: &mut Transaction, variant: MtxVariant, account: Uuid) -> Result { let query = " SELECT id, account, variant FROM mtx WHERE account = $1 AND variant = $2 FOR UPDATE; "; let result = tx .query(query, &[&account, &variant.to_sql()])?; if let Some(row) = result.iter().next() { let id: Uuid = row.get(0); let account: Uuid = row.get(1); let v_str: String = row.get(2); let variant = MtxVariant::try_from(v_str)?; Ok(Mtx { id, account, variant }) } else { Err(format_err!("mtx not found account={:?} variant={:?}", account, variant)) } } pub fn new_construct(tx: &mut Transaction, account: &Account) -> Result { account::debit(tx, account.id, NEW_CONSTRUCT_COST)?; let construct = construct_spawn(tx, account.id, generate_name(), false)?; Ok(construct) } pub fn account_shop(tx: &mut Transaction, account: &Account) -> Result { let query = " SELECT id, variant FROM mtx WHERE account = $1; "; let result = tx .query(query, &[&account.id])?; let mut owned = vec![]; for row in result.iter() { let id: Uuid = row.get(0); let v_str: String = row.get(1); let variant = match MtxVariant::try_from(v_str) { Ok(v) => v, Err(e) => { warn!("id={:?} {:?}", id, e); continue; }, }; owned.push(variant); }; let available = SHOP_LISTINGS.iter() .filter(|l| !owned.contains(&l.variant)) .map(|l| *l) .collect::>(); debug!("account shop acount={:?} owned={:?} available={:?}", account.name, owned, available); return Ok(Shop { owned, available }); } pub fn buy(tx: &mut Transaction, account: &Account, mtx: MtxVariant) -> Result { let listing = SHOP_LISTINGS .iter() .find(|l| l.variant == mtx) .ok_or(format_err!("mtx variant not found variant={:?}", mtx))?; let account = account::debit(tx, account.id, listing.credits as i64)?; Mtx::new(mtx, account.id) .insert(tx)?; info!("account purchased mtx account={:?} mtx={:?} cost={:?} balance={:?}", account.name, mtx, listing.credits, account.balance); account_shop(tx, &account) }