271 lines
7.3 KiB
Rust
271 lines
7.3 KiB
Rust
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 mnml_core::construct::{Construct};
|
|
|
|
use pg::{construct_select, construct_write, construct_spawn};
|
|
use names::{name as generate_name};
|
|
use img;
|
|
|
|
pub const FREE_MTX: [MtxVariant; 2] = [
|
|
MtxVariant::Rename,
|
|
MtxVariant::Shapes,
|
|
];
|
|
|
|
pub const SHOP_LISTINGS: [Listing; 2] = [
|
|
Listing { variant: MtxVariant::Invader, credits: 10 },
|
|
Listing { variant: MtxVariant::Smile, 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<MtxVariant>,
|
|
available: Vec<Listing>,
|
|
}
|
|
|
|
#[derive(Debug,Copy,Clone,PartialEq,Serialize,Deserialize)]
|
|
pub struct Listing {
|
|
variant: MtxVariant,
|
|
credits: usize,
|
|
}
|
|
|
|
#[derive(Debug,Copy,Clone,PartialEq,Serialize,Deserialize)]
|
|
pub enum MtxVariant {
|
|
Rename,
|
|
Invader,
|
|
Shapes,
|
|
Smile,
|
|
}
|
|
|
|
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<String> for MtxVariant {
|
|
type Error = Error;
|
|
fn try_from(v: String) -> Result<MtxVariant, Error> {
|
|
match v.as_ref() {
|
|
"Rename" => Ok(MtxVariant::Rename),
|
|
"Invader" => Ok(MtxVariant::Invader),
|
|
"Smile" => Ok(MtxVariant::Smile),
|
|
"Shapes" => Ok(MtxVariant::Shapes),
|
|
_ => 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 },
|
|
}
|
|
}
|
|
|
|
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<Vec<Construct>, 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::Shapes => img::shapes_write(construct.img)?,
|
|
MtxVariant::Smile => img::smile(construct.img)?,
|
|
_ => construct.img,
|
|
};
|
|
|
|
construct_write(tx, construct)?;
|
|
account::team(tx, account)
|
|
}
|
|
|
|
pub fn account_apply(tx: &mut Transaction, account: &Account, variant: MtxVariant) -> Result<Account, Error> {
|
|
let mtx = select(tx, variant, account.id)?;
|
|
let cost = match mtx.variant {
|
|
MtxVariant::Rename => return Err(err_msg("rename not usable on account")),
|
|
_ => NEW_IMAGE_COST,
|
|
};
|
|
|
|
account::debit(tx, account.id, cost)?;
|
|
let account = account::new_img(tx, account.id)?;
|
|
|
|
match mtx.variant {
|
|
MtxVariant::Invader => img::invader_write(account.img)?,
|
|
MtxVariant::Shapes => img::shapes_write(account.img)?,
|
|
MtxVariant::Smile => img::smile(account.img)?,
|
|
_ => account.img,
|
|
};
|
|
|
|
Ok(account)
|
|
}
|
|
|
|
pub fn select(tx: &mut Transaction, variant: MtxVariant, account: Uuid) -> Result<Mtx, Error> {
|
|
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<Construct, Error> {
|
|
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<Shop, Error> {
|
|
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::<Vec<Listing>>();
|
|
|
|
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<Shop, Error> {
|
|
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)
|
|
} |