Merge branch 'sql'

This commit is contained in:
ntr
2018-09-13 17:20:17 +10:00
24 changed files with 222 additions and 37 deletions
+3
View File
@@ -0,0 +1,3 @@
[target.x86_64-pc-windows-msvc.gnu]
rustc-link-search = ["C:\\Program Files\\PostgreSQL\\pg96\\lib"]
Executable
+1
View File
@@ -0,0 +1 @@
DATABASE_URL=postgres://cryps:craftbeer@localhost/cryps
+2
View File
@@ -0,0 +1,2 @@
target/
Cargo.lock
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "cryps"
version = "0.1.0"
authors = ["ntr <ntr@smokestack.io>"]
[dependencies]
rand = "0.5"
uuid = { version = "0.5", features = ["serde", "v4"] }
serde = "1"
serde_derive = "1"
serde_cbor = "0.9"
ws = "*"
dotenv = "0.9.0"
env_logger = "*"
r2d2 = "0.8.2"
r2d2_sqlite = "0.6"
rusqlite = { version = "0.14.0", features = ["bundled"] }
+11
View File
@@ -0,0 +1,11 @@
## 02-09-2018
* went full circle through the last 20 years of the web's problems
* debated using vanilla tcp sockets but realised would be very time consuming
* Struggled a lot with google/tarpc
found the documentation absolutely mad, macros perform most of the functionality
couldn't find any way to keep server running, client stub appears to direcly rely on the server structs
needed a specific version of the rust nightly from several months ago to compile
* found wa-rs, hope was restored, had a websocket server up and running in seconds
* lost hope again when its client doesn't compile into wasm due to unix dependencies in mio
this also prevents any tokio based futures client from working
* realised i'd been reading very out of date documentation and there was plenty of work happening on `stdweb` and `cargo web`
+97
View File
@@ -0,0 +1,97 @@
# Cryps ("creeps") // Creeptography
## Setup
```
rustup default nightly-2018-06-09-x86_64-unknown-linux-gnu
```
## Items
## Rolling
stat & rng
block hash or totally random
roll server that prints a roll every second?
friendship on ties?
Def
0001011011001100110101000000101111100110101111110100100001000001 - roll
0000000000000000000000000000000000000000000000000000000000001111 ^ steel armour
0000000000000000000000000000000000000000000000000000001111000000 ^ stoney trait
0001011011001100110101000000101111100110101111110100101111001111 = modified roll
0000000000000000000000000000000000000000000000000000000111000010 & def attribute
0000000000000000000000000000000000000000000000000000000111000010 = roll w/ stats
0000000000000000000000000000000000000000000000000000000001000000 = roll w/out stats
## missions
also the idea is like
the currency is kinda like path right
you're trying to get like chaos
to reroll some stat
or some item
so maybe like a sword
has 5 bits of damage it can guarantee
but a badman-sabre has 16 bits
and you keep blowing chaos on it til it gets 1111111111
MashyToday at 9:04 PM
yeah that would be cool
natureToday at 9:05 PM
i feel like that would make it kinda p2w
so probably needs limits
MashyToday at 9:05 PM
I was thinking the p2w more just like making the needs and missions quicker right
so like instead of feeding your dude you get some item where hes fed for 2 days or something
or you could reduce the mission time which is like kinda pay to win but not really
natureToday at 9:06 PM
well what do the missions give you
MashyToday at 9:06 PM
well thats what i was thinking you'd do to get items
so instead of doing a zone in an rpg and getting all this loot
you send your dude out and you got a notification 4 hours later like success / failure this is what you got back
natureToday at 9:07 PM
i was thinking aobut that
i don't really like the possibility of failure on a mission
imagine sending your dude out on a mission for 2 days and it ceoms back like "nope wrong"
i'd just fucken rq
MashyToday at 9:08 PM
Yeah
natureToday at 9:08 PM
BUT
a better thing is like
playing off crypto
MashyToday at 9:08 PM
its something like this https://www.youtube.com/watch?v=bXLW9dF7LN8
YouTube
Tommy J
WoW: Garrison Mission Chance - How it Works
natureToday at 9:08 PM
missions are like
MashyToday at 9:08 PM
so you get told before hand the % chance for success
natureToday at 9:08 PM
go and do 2000 overkill damage to some monster
and your dude is out there using his items and rolling
so it can happen quick if you're lucky
or slow if you're not
but still will eventually happen
and then you get like easy limits for missions right
MashyToday at 9:09 PM
yeah that would be better actually
natureToday at 9:09 PM
like a creep that onl deals 100 dmg can't finish that mission
i feel like that would be good
cause then if you have like a defensive cryp
it could have a defensive kinda mission
like go and block 40000 damage in pve
and then it gets some baller shield
and is like speccing into defense
MashyToday at 9:10 PM
sounds better
natureToday at 9:10 PM
and like an offensive cryp could do that too
but it might keep getting KOd
and you hvae to pay to revive it
+15
View File
@@ -0,0 +1,15 @@
* Battling
* Logins
* Cryp Ownership
* Matchmaking
* Lobbies
* Create
* Join
* Resolve
* Stats
* Missions
* Cryp Generation
*
* Blockchain Integration?
+57
View File
@@ -0,0 +1,57 @@
// use uuid::Uuid;
use rand::prelude::*;
use cryp::Cryp;
// impl Attribute {
// pub fn as_str(&self) -> &'static str {
// match self {
// Dmg => "dmg",
// Def => "def",
// }
// }
// }
#[derive(Debug)]
pub struct Battle {
a: Cryp,
b: Cryp,
}
impl Battle {
pub fn new(a: &Cryp, b: &Cryp) -> Battle {
return Battle {
a: a.clone(),
b: b.clone(),
};
}
pub fn cryps(&self) -> Vec<&Cryp> {
vec![&self.a, &self.b]
}
pub fn next(&mut self) -> &mut Battle {
let a_turn = self.a.turn();
let b_turn = self.b.turn();
self.a.assign_dmg(&self.b, &a_turn, &b_turn);
self.b.assign_dmg(&self.a, &b_turn, &a_turn);
self
}
pub fn finished(&self) -> bool {
self.cryps().iter().any(|c| c.is_ko())
}
pub fn winner(&self) -> Option<&Cryp> {
if self.cryps().iter().all(|c| c.is_ko()) {
return None
}
match self.cryps().iter().find(|c| !c.is_ko()) {
Some(w) => Some(w),
None => panic!("no winner found {:?}", self),
}
}
}
+113
View File
@@ -0,0 +1,113 @@
use rand::prelude::*;
use cryp::Cryp;
use battle::Battle;
use skill::Skill;
struct Encounter {
mob: Cryp,
success: bool,
player: Cryp,
}
pub fn battle(a: &Cryp, b: &Cryp) -> Battle {
let mut battle = Battle::new(a, b);
loop {
battle.next();
if battle.finished() {
break battle
}
}
}
fn pve(plr: Cryp) -> Encounter {
let mut rng = thread_rng();
let mob_lvl: u8 = rng.gen_range(1, plr.lvl);
let mob = Cryp::new()
.named("bamboo basher".to_string())
.level(mob_lvl)
.create();
let outcome = battle(&plr, &mob);
let success = match outcome.winner() {
Some(c) => c.id == plr.id,
None => false,
};
return Encounter {
mob: mob,
success,
player: plr,
};
}
pub fn levelling(mut c: Cryp) -> Cryp {
loop {
let enc = pve(c);
c = enc.player;
if !enc.success {
println!("{:?} has been KO'd", c.name);
break c;
}
println!("{:?} rekt {:?}", c.name, enc.mob.name);
c = c.add_xp();
println!("{:?} now has {:?} xp and is lvl {:?}", c.name, c.xp, c.lvl);
// LEVEL CAP
if c.lvl == 12 {
break c;
}
continue;
}
}
pub fn test_battle() {
let mut a = Cryp::new()
.named("pronounced \"creeep\"".to_string())
.level(8)
.learn(Skill::Stoney)
.create();
let b = Cryp::new()
.named("lemongrass tea".to_string())
.level(8)
.create();
let outcome = battle(&a, &b);
match outcome.winner() {
Some(w) => println!("{:?} is the winner with {:?} hp remaining", w.name, w.hp),
None => println!("{:?} was a draw", outcome),
};
return
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pve_test() {
let player = Cryp::new()
.named("ca phe sua da".to_string())
.level(2)
.create();
levelling(player);
return;
}
#[test]
fn battle_test() {
test_battle();
return;
}
}
+209
View File
@@ -0,0 +1,209 @@
use uuid::Uuid;
use rand::prelude::*;
use serde_cbor::*;
use std::fs::File;
use std::io::prelude::*;
use rpc::{GenerateParams};
use skill::{Skill};
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum StatKind {
Dmg,
Def,
Hp,
Stam,
}
#[derive(Debug,Clone)]
pub struct Roll {
pub base: u64,
pub result: u64,
pub kind: StatKind,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Stat {
pub value: u64,
pub kind: StatKind,
}
impl Stat {
fn set(&mut self, v: u64) -> &Stat {
self.value = v;
self
}
fn roll(&self, c: &Cryp) -> Roll {
let mut rng = thread_rng();
let base: u64 = rng.gen();
let mut roll = Roll { kind: self.kind, base, result: base };
println!("{:?}", self.kind);
println!("{:064b} <- base roll", base);
// apply skills
roll = c.skills.iter().fold(roll, |roll, s| s.apply(roll));
// finally combine with stat
println!("{:064b} <- finalised", roll.result);
roll.result = roll.result & self.value;
println!("{:064b} & <- attribute roll", self.value);
println!("{:064b} = {:?}", roll.result, roll.result);
println!("");
return roll;
}
fn reduce(&mut self, dmg: u64) -> &mut Stat {
self.value = self.value.saturating_sub(dmg);
self
}
}
pub struct Turn {
pub dmg: Roll,
pub def: Roll,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Cryp {
pub id: Uuid,
// todo
// make attributes hold this value
pub dmg: Stat,
pub def: Stat,
pub stam: Stat,
pub hp: Stat,
pub xp: u64,
pub lvl: u8,
pub skills: Vec<Skill>,
pub name: String,
}
fn check_lvl(lvl: u8) -> u8 {
if lvl > 64 { return 64; }
return lvl;
}
impl Cryp {
pub fn new() -> Cryp {
let id = Uuid::new_v4();
return Cryp {
id,
dmg: Stat { value: 0, kind: StatKind::Dmg },
def: Stat { value: 0, kind: StatKind::Def },
stam: Stat { value: 0, kind: StatKind::Stam },
hp: Stat { value: 0, kind: StatKind::Hp },
lvl: 0,
xp: 0,
skills: vec![],
name: String::new()
};
}
pub fn named(mut self, name: String) -> Cryp {
self.name = name.clone();
self
}
pub fn level(mut self, lvl: u8) -> Cryp {
self.lvl = check_lvl(lvl);
self
}
pub fn learn(mut self, s: Skill) -> Cryp {
self.skills.push(s);
self
}
pub fn add_xp(mut self) -> Cryp {
self.xp = self.xp.saturating_add(1);
if self.xp.is_power_of_two() {
return self.level_up();
}
self
}
pub fn level_up(mut self) -> Cryp {
self.lvl = self.lvl.saturating_add(1);
self.create()
}
pub fn turn(&self) -> Turn {
// println!("{:?}'s turn:", c.name);
let dmg = self.dmg.roll(self);
let def = self.def.roll(self);
return Turn { dmg, def }
}
pub fn create(mut self) -> Cryp {
let mut rng = thread_rng();
let max = match self.lvl == 64 {
true => u64::max_value(),
false => 2_u64.pow(self.lvl.into()),
};
self.xp = max;
self.dmg.set(rng.gen_range(1, max));
self.def.set(rng.gen_range(1, max));
self.stam.set(rng.gen_range(1, max));
self.hp.set(self.stam.value);
self
}
pub fn assign_dmg(&mut self, opp: &Cryp, plr_t: &Turn, opp_t: &Turn) -> &mut Cryp {
let final_dmg = opp_t.dmg.result.saturating_sub(plr_t.def.result);
let blocked = opp_t.dmg.result.saturating_sub(final_dmg);
self.hp.reduce(final_dmg);
println!("{:?} deals {:?} dmg to {:?} ({:?} blocked / {:?} hp remaining)"
,opp.name
,final_dmg
,self.name
,blocked
,self.hp.value);
self
}
pub fn is_ko(&self) -> bool {
self.hp.value == 0
}
}
pub fn generate(params: GenerateParams) -> Vec<u8> {
let level_two = Cryp::new()
.named("hatchling".to_string())
.level(params.level)
.learn(Skill::Stoney)
.create();
to_vec(&level_two)
.expect("couldn't serialize cryp")
}
#[cfg(test)]
mod tests {
use cryp::*;
use skill::*;
#[test]
fn create_cryp_test() {
let max_level = Cryp::new()
.named("hatchling".to_string())
.level(64)
.learn(Skill::Stoney)
.create();
assert_eq!(max_level.lvl, 64);
return;
}
}
+29
View File
@@ -0,0 +1,29 @@
extern crate rand;
extern crate uuid;
extern crate ws;
extern crate env_logger;
// #[macro_use]
// extern crate dotenv;
extern crate r2d2;
extern crate r2d2_sqlite;
extern crate rusqlite;
extern crate serde;
extern crate serde_cbor;
#[macro_use]
extern crate serde_derive;
mod cryp;
mod battle;
mod net;
mod combat;
mod skill;
mod rpc;
mod user;
use net::{start};
fn main() {
start()
}
+55
View File
@@ -0,0 +1,55 @@
use ws::{listen, Handler, Sender, Result, Message, Handshake, CloseCode, Error};
use serde_cbor::{to_vec};
use r2d2::{Pool};
use r2d2::{PooledConnection};
use r2d2_sqlite::{SqliteConnectionManager};
pub type Db = PooledConnection<SqliteConnectionManager>;
use cryp::{generate};
use rpc::{Rpc,RpcMessage};
struct Server {
out: Sender,
rpc: Rpc,
db: Pool<SqliteConnectionManager>,
}
impl Handler for Server {
fn on_open(&mut self, _: Handshake) -> Result<()> {
println!("somebody joined");
Ok(())
}
fn on_message(&mut self, msg: Message) -> Result<()> {
let db = self.db.get().expect("unable to get db connection");
let reply = self.rpc.receive(msg, db);
println!("{:?}", reply);
self.out.send(reply.unwrap())
}
fn on_close(&mut self, code: CloseCode, reason: &str) {
match code {
CloseCode::Normal => println!("The client is done with the connection."),
CloseCode::Away => println!("The client is leaving the site."),
CloseCode::Abnormal => println!(
"Closing handshake failed! Unable to obtain closing status from client."),
_ => println!("The client encountered an error: {}", reason),
}
}
fn on_error(&mut self, err: Error) {
println!("The server encountered an error: {:?}", err);
}
}
pub fn start() {
let manager = SqliteConnectionManager::file("/var/cryps/cryps.db");
let pool = Pool::builder()
.build(manager)
.expect("Failed to create pool.");
listen("127.0.0.1:40000", |out| { Server { out, rpc: Rpc {}, db: pool.clone() } }).unwrap();
}
+90
View File
@@ -0,0 +1,90 @@
use std::result::Result as StdResult;
use ws::{Message};
use serde_cbor::{from_slice};
use serde_cbor::error::Error as CborError;
use net::Db;
use cryp::generate;
use user::{create};
pub struct Rpc;
impl Rpc {
pub fn receive(&self, msg: Message, db: Db) -> StdResult<Vec<u8>, RpcError> {
// consume the ws data into bytes
let data = msg.into_data();
// cast the msg to this type to receive method name
match from_slice::<RpcMessage>(&data) {
Ok(v) => {
// now we have the method name
// match on that to determine what fn to call
match v.method.as_ref() {
"cryp_generate" => {
match from_slice::<GenerateMsg>(&data) {
Ok(v) => Ok(generate(v.params)),
Err(_) => Err(RpcError::Parse),
}
},
"account_create" => {
match from_slice::<AccountCreateMsg>(&data) {
Ok(v) => Ok(create(v.params, db)),
Err(_) => Err(RpcError::Parse),
}
},
_ => Err(RpcError::UnknownMethod),
}
},
Err(_) => Err(RpcError::Parse),
}
}
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct RpcMessage {
method: String,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub enum RpcError {
Parse,
UnknownMethod,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
struct GenerateMsg {
method: String,
params: GenerateParams,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct GenerateParams {
pub level: u8,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
struct AccountCreateMsg {
method: String,
params: AccountCreateParams,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct AccountCreateParams {
pub name: String,
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use serde_cbor::to_vec;
// #[test]
// fn rpc_parse() {
// let rpc = Rpc {};
// let msg = GenerateMsg { method: "cryp_generate".to_string(), params: GenerateParams { level: 64 } };
// let v = to_vec(&msg).unwrap();
// let received = rpc.receive(Message::Binary(v));
// }
// }
+41
View File
@@ -0,0 +1,41 @@
use cryp::{StatKind, Roll};
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Skill {
Stoney,
Evasive,
}
impl Skill {
pub fn apply(&self, mut roll: Roll) -> Roll {
match self {
Skill::Stoney => stoney(self, roll),
Skill::Evasive => evasive(self, roll),
_ => panic!("missing skill"),
}
}
}
fn stoney(s: &Skill, mut roll: Roll) -> Roll {
let effect = 0b11110000;
match roll.kind {
StatKind::Def => {
println!("{:064b} | <- {:?}", effect, s);
roll.result = roll.result | effect;
roll
},
_ => roll,
}
}
fn evasive(s: &Skill, mut roll: Roll) -> Roll {
match roll.kind {
StatKind::Def => {
if roll.result.is_power_of_two() {
roll.result = u64::max_value()
}
roll
},
_ => roll,
}
}
+29
View File
@@ -0,0 +1,29 @@
use serde_cbor::to_vec;
use uuid::Uuid;
use net::Db;
use rpc::{AccountCreateParams};
struct User {
name: String,
id: Uuid,
}
pub fn create(params: AccountCreateParams, db: Db) -> Vec<u8> {
let uuid = Uuid::new_v4();
let user = User {
id: uuid,
name: params.name,
};
let entry = db.execute("INSERT INTO users (id, name)
VALUES (?1, ?2)",
&[&user.id.to_string(), &user.name]).unwrap();
println!("{:?}", entry);
match to_vec(&true) {
Ok(v) => v,
Err(e) => panic!("couldn't serialize cryp"),
}
}