Merge branch 'master' of http://gogs.scratch.com:59515/local/secret-hitler
commit
98baeb154e
|
|
@ -0,0 +1,272 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maybe-uninit"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
|
||||
|
||||
[[package]]
|
||||
name = "secert-hitler"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"env_logger",
|
||||
"json",
|
||||
"log",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "secert-hitler"
|
||||
version = "0.1.0"
|
||||
authors = ["bel <squeaky2x3@blapointe.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "*"
|
||||
crossbeam-channel = "*"
|
||||
json = "*"
|
||||
env_logger = "*"
|
||||
log = "*"
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
pub static PORT:&str = ":8080";
|
||||
pub static MIN_PLAYERS:usize = 5;
|
||||
pub static MAX_PLAYERS:usize = 10;
|
||||
|
||||
pub fn players_to_facists(n: usize) -> Result<usize, String> {
|
||||
match n {
|
||||
5 => Ok(2),
|
||||
6 => Ok(2),
|
||||
7 => Ok(3),
|
||||
8 => Ok(3),
|
||||
9 => Ok(4),
|
||||
10 => Ok(4),
|
||||
_ => Err("unsupported number of players".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn players_to_policies_facist(n: usize) -> usize {
|
||||
11 - (n-5)/2
|
||||
}
|
||||
|
||||
pub fn players_to_policies_liberal(_: usize) -> usize {
|
||||
6
|
||||
}
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
use super::super::super::model::state::event::Event;
|
||||
use json;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GameEvent {
|
||||
pub d: json::JsonValue,
|
||||
pub sender: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum GameEventType {
|
||||
Null,
|
||||
GameStart,
|
||||
RoleSet,
|
||||
ElectionPend,
|
||||
ElectionSet,
|
||||
VoteSet,
|
||||
VoteFailed,
|
||||
CardPend,
|
||||
CardPick,
|
||||
PolicySet,
|
||||
SpecialInspect,
|
||||
SpecialSelect,
|
||||
SpecialKill,
|
||||
SpecialPeek,
|
||||
GameStop,
|
||||
}
|
||||
|
||||
impl GameEventType {
|
||||
pub fn from_string(s: String) -> GameEventType {
|
||||
let cases = vec![
|
||||
GameEventType::Null,
|
||||
GameEventType::GameStart,
|
||||
GameEventType::RoleSet,
|
||||
GameEventType::ElectionPend,
|
||||
GameEventType::ElectionSet,
|
||||
GameEventType::VoteSet,
|
||||
GameEventType::CardPend,
|
||||
GameEventType::CardPick,
|
||||
GameEventType::PolicySet,
|
||||
GameEventType::SpecialInspect,
|
||||
GameEventType::SpecialSelect,
|
||||
GameEventType::SpecialKill,
|
||||
GameEventType::SpecialPeek,
|
||||
GameEventType::GameStop,
|
||||
];
|
||||
for c in &cases {
|
||||
if format!("{:?}", c) == s {
|
||||
return c.clone();
|
||||
}
|
||||
}
|
||||
GameEventType::Null
|
||||
}
|
||||
|
||||
pub fn build(&self) -> GameEvent {
|
||||
let d = json::object!{
|
||||
"body": json::object!{
|
||||
GameEventType: format!("{:?}", self),
|
||||
sources: [],
|
||||
targets: [],
|
||||
params: [],
|
||||
}.dump(),
|
||||
};
|
||||
GameEvent::new(Event{sender: "__gm__".to_string(), body: d.dump(), since: "".to_string()})
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEvent {
|
||||
pub fn new(event: Event) -> GameEvent {
|
||||
let top_level = json::parse(&event.body.clone());
|
||||
if top_level.is_err() {
|
||||
return GameEvent{d: json::Null, sender: event.sender.clone()};
|
||||
}
|
||||
let top_level = top_level.unwrap();
|
||||
if !top_level["body"].is_string() {
|
||||
return GameEvent{d: json::Null, sender: event.sender.clone()};
|
||||
}
|
||||
let body = top_level["body"].as_str().unwrap();
|
||||
let d = json::parse(&body).unwrap_or(json::Null);
|
||||
let sender = event.sender.clone();
|
||||
if d.is_object() && d.has_key("sender") && d["sender"].is_string() {
|
||||
// sender = d["sender"].as_str().unwrap();
|
||||
}
|
||||
GameEvent{
|
||||
d: d,
|
||||
sender: sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> GameEventType {
|
||||
if self.d.is_null() {
|
||||
return GameEventType::Null;
|
||||
}
|
||||
if !self.d["GameEventType"].is_string() {
|
||||
return GameEventType::Null;
|
||||
}
|
||||
let s = self.d["GameEventType"].as_str().unwrap().to_string();
|
||||
GameEventType::from_string(s.to_string())
|
||||
}
|
||||
|
||||
pub fn sources(&self) -> Vec<String> {
|
||||
self.str_vec("sources")
|
||||
}
|
||||
|
||||
pub fn targets(&self) -> Vec<String> {
|
||||
self.str_vec("targets")
|
||||
}
|
||||
|
||||
pub fn params(&self) -> Vec<String> {
|
||||
self.str_vec("params")
|
||||
}
|
||||
|
||||
pub fn str_vec(&self, key: &str) -> Vec<String> {
|
||||
let mut out = vec![];
|
||||
if self.d[key].is_string() {
|
||||
out.push(self.d[key].as_str().unwrap().to_string());
|
||||
return out;
|
||||
}
|
||||
if self.d[key].is_array() {
|
||||
let iter = self.d[key].members();
|
||||
for i in iter {
|
||||
if !i.is_string() {
|
||||
return vec![];
|
||||
}
|
||||
out.push(i.as_str().unwrap().to_string());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> String {
|
||||
(json::object!{
|
||||
body: json::stringify(json::object!{
|
||||
GameEventType: format!("{:?}", self.mode()),
|
||||
sources: self.sources(),
|
||||
targets: self.targets(),
|
||||
params: self.params(),
|
||||
}),
|
||||
msgtype: "m.text".to_string(),
|
||||
}).dump()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use log::{debug, LevelFilter};
|
||||
|
||||
fn init() {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter_level(LevelFilter::Trace)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
let ge = GameEvent::new(Event{sender: "".to_string(), body: "\"a\"".to_string(), since: "".to_string()});
|
||||
assert!(ge.d.is_null());
|
||||
|
||||
let ge = GameEvent::new(Event{sender: "".to_string(), body: "a".to_string(), since: "".to_string()});
|
||||
assert!(ge.d.is_null());
|
||||
assert!(ge.mode() == GameEventType::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_from_string() {
|
||||
assert!(GameEventType::from_string("Null".to_string()) == GameEventType::Null);
|
||||
assert!(GameEventType::from_string("GameStart".to_string()) == GameEventType::GameStart);
|
||||
assert!(GameEventType::from_string("RoleSet".to_string()) == GameEventType::RoleSet);
|
||||
assert!(GameEventType::from_string("ElectionPend".to_string()) == GameEventType::ElectionPend);
|
||||
assert!(GameEventType::from_string("ElectionSet".to_string()) == GameEventType::ElectionSet);
|
||||
assert!(GameEventType::from_string("VoteSet".to_string()) == GameEventType::VoteSet);
|
||||
assert!(GameEventType::from_string("CardPend".to_string()) == GameEventType::CardPend);
|
||||
assert!(GameEventType::from_string("CardPick".to_string()) == GameEventType::CardPick);
|
||||
assert!(GameEventType::from_string("PolicySet".to_string()) == GameEventType::PolicySet);
|
||||
assert!(GameEventType::from_string("SpecialInspect".to_string()) == GameEventType::SpecialInspect);
|
||||
assert!(GameEventType::from_string("SpecialSelect".to_string()) == GameEventType::SpecialSelect);
|
||||
assert!(GameEventType::from_string("SpecialKill".to_string()) == GameEventType::SpecialKill);
|
||||
assert!(GameEventType::from_string("SpecialPeek".to_string()) == GameEventType::SpecialPeek);
|
||||
assert!(GameEventType::from_string("GameStop".to_string()) == GameEventType::GameStop);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_build() {
|
||||
let cases = vec![
|
||||
GameEventType::Null,
|
||||
GameEventType::GameStart,
|
||||
GameEventType::RoleSet,
|
||||
GameEventType::ElectionPend,
|
||||
GameEventType::ElectionSet,
|
||||
GameEventType::VoteSet,
|
||||
GameEventType::CardPend,
|
||||
GameEventType::CardPick,
|
||||
GameEventType::PolicySet,
|
||||
GameEventType::SpecialInspect,
|
||||
GameEventType::SpecialSelect,
|
||||
GameEventType::SpecialKill,
|
||||
GameEventType::SpecialPeek,
|
||||
GameEventType::GameStop,
|
||||
];
|
||||
for c in &cases {
|
||||
let gameevent = c.build();
|
||||
assert!(*c == gameevent.mode(), "{:?}.build() yielded {:?}", c, gameevent);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sources() {
|
||||
let d = json::object!{"body": json::object!{"sources": ["a", "b"]}.dump()};
|
||||
let ge = GameEvent::new(Event{sender: "".to_string(), body: d.dump(), since: "".to_string()});
|
||||
assert!(ge.sources() == vec!["a", "b"]);
|
||||
assert!(ge.params().len() == 0);
|
||||
assert!(ge.targets().len() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn targets() {
|
||||
let d = json::object!{"body": json::object!{"targets": ["a", "b"]}.dump()};
|
||||
let ge = GameEvent::new(Event{sender: "".to_string(), body: d.dump(), since: "".to_string()});
|
||||
assert!(ge.targets() == vec!["a", "b"]);
|
||||
assert!(ge.params().len() == 0);
|
||||
assert!(ge.sources().len() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn params() {
|
||||
let empty: Vec<String> = vec![];
|
||||
|
||||
let d = json::object!{"body": json::object!{"params": ["a", "b"]}.dump()};
|
||||
let ge = GameEvent::new(Event{sender: "".to_string(), body: d.dump(), since: "".to_string()});
|
||||
assert!(ge.params() == vec!["a", "b"]);
|
||||
assert!(ge.targets().len() == 0);
|
||||
assert!(ge.sources().len() == 0);
|
||||
|
||||
let d = json::object!{"body": json::object!{"params": []}.dump()};
|
||||
let ge = GameEvent::new(Event{sender: "".to_string(), body: d.dump(), since: "".to_string()});
|
||||
assert!(ge.params() == empty);
|
||||
assert!(ge.targets().len() == 0);
|
||||
assert!(ge.sources().len() == 0);
|
||||
|
||||
let d = json::object!{"body": json::object!{}.dump()};
|
||||
let ge = GameEvent::new(Event{sender: "".to_string(), body: d.dump(), since: "".to_string()});
|
||||
assert!(ge.params() == empty);
|
||||
assert!(ge.targets().len() == 0);
|
||||
assert!(ge.sources().len() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
init();
|
||||
let ge = GameEvent{
|
||||
d: json::object!{},
|
||||
sender: "a".to_string(),
|
||||
};
|
||||
assert!(ge.serialize() == r#"{"body":"{\"GameEventType\":\"Null\",\"sources\":[],\"targets\":[],\"params\":[]}","msgtype":"m.text"}"#, "{}", ge.serialize());
|
||||
|
||||
let ge = GameEvent{
|
||||
d: json::object!{GameEventType: "GameStart"},
|
||||
sender: "a".to_string(),
|
||||
};
|
||||
assert!(ge.serialize() == r#"{"body":"{\"GameEventType\":\"GameStart\",\"sources\":[],\"targets\":[],\"params\":[]}","msgtype":"m.text"}"#, "{}", ge.serialize());
|
||||
|
||||
let ge = GameEvent{
|
||||
d: json::object!{GameEventType: "GameStart", params: ["hi"]},
|
||||
sender: "a".to_string(),
|
||||
};
|
||||
assert!(ge.serialize() == r#"{"body":"{\"GameEventType\":\"GameStart\",\"sources\":[],\"targets\":[],\"params\":[\"hi\"]}","msgtype":"m.text"}"#, "{}", ge.serialize());
|
||||
|
||||
let ge = GameEvent{
|
||||
d: json::object!{GameEventType: "GameStart", sources: ["hi"], targets: ["hi2"]},
|
||||
sender: "a".to_string(),
|
||||
};
|
||||
assert!(ge.serialize() == r#"{"body":"{\"GameEventType\":\"GameStart\",\"sources\":[\"hi\"],\"targets\":[\"hi2\"],\"params\":[]}","msgtype":"m.text"}"#, "{}", ge.serialize());
|
||||
|
||||
let ge = GameEvent{
|
||||
d: json::object!{GameEventType: "Null", sources: ["hi"], targets: ["hi2"]},
|
||||
sender: "a".to_string(),
|
||||
};
|
||||
assert!(ge.serialize() == r#"{"body":"{\"GameEventType\":\"Null\",\"sources\":[\"hi\"],\"targets\":[\"hi2\"],\"params\":[]}","msgtype":"m.text"}"#, "{}", ge.serialize());
|
||||
debug!("sample gameevent serialize: {}", ge.serialize());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,600 @@
|
|||
use super::gamemaster::GameMaster;
|
||||
use super::super::gameevent::GameEvent;
|
||||
use super::super::gameevent::GameEventType;
|
||||
use super::super::policy::Policy;
|
||||
use super::super::role::Role;
|
||||
|
||||
use log::{debug, error};
|
||||
use json;
|
||||
use std::collections::HashMap;
|
||||
|
||||
impl GameMaster {
|
||||
pub fn run_game(&mut self) -> Result<GameEvent, Role> {
|
||||
loop {
|
||||
let ge = self.game_is_over()?;
|
||||
self.room.send(ge.serialize());
|
||||
let ge = self.game_election()?;
|
||||
self.room.send(ge.serialize());
|
||||
let ge = self.game_is_over()?;
|
||||
self.room.send(ge.serialize());
|
||||
let p = match self.game_election_vote().unwrap_or(GameEventType::GameStop.build()).mode() {
|
||||
GameEventType::VoteFailed => self.game_policy_select_random(),
|
||||
GameEventType::GameStop => self.game_is_over(),
|
||||
_ => self.game_policy_select(),
|
||||
}?;
|
||||
self.room.send(p.serialize());
|
||||
if p.mode() == GameEventType::Null {
|
||||
continue;
|
||||
} else if p.mode() != GameEventType::PolicySet {
|
||||
error!("unexpected game event type after election vote followup: {:?}", p);
|
||||
return Err(Role::Null);
|
||||
}
|
||||
let params = p.params();
|
||||
let param = params.first();
|
||||
if param.is_none() {
|
||||
error!("unexpected missing param on {:?}: {:?}", p, param);
|
||||
return Err(Role::Null);
|
||||
}
|
||||
let param = param.unwrap();
|
||||
let policy = Policy::from_string(param.to_string());
|
||||
let ge = self.game_policy_veto(policy.clone())?;
|
||||
self.room.send(ge.serialize());
|
||||
let ge = self.game_ends_with(policy.clone())?;
|
||||
self.room.send(ge.serialize());
|
||||
let ge = self.game_policy_enact(policy.clone())?;
|
||||
self.room.send(ge.serialize());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn game_is_over(&mut self) -> Result<GameEvent, Role> {
|
||||
if self.policies[&Policy::Facist] >= 3 {
|
||||
let chancellor = self.chancellor.clone();
|
||||
if chancellor.is_some() {
|
||||
let player = self.player(chancellor.unwrap());
|
||||
if player.is_some() && player.unwrap().get_role() == Role::Hitler {
|
||||
return Err(Role::Facist);
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.policies[&Policy::Facist] > 5 {
|
||||
return Err(Role::Facist);
|
||||
}
|
||||
if self.policies[&Policy::Liberal] > 5 {
|
||||
return Err(Role::Liberal);
|
||||
}
|
||||
Ok(GameEventType::Null.build())
|
||||
}
|
||||
|
||||
pub fn game_election(&mut self) -> Result<GameEvent, Role> {
|
||||
let mut ge = GameEventType::ElectionPend.build();
|
||||
let president_candidate = self.candidate_presidents.pop().unwrap();
|
||||
self.president = Some(president_candidate.clone());
|
||||
ge.d["targets"] = json::array![president_candidate.clone()];
|
||||
ge.d["params"] = json::array!["president"];
|
||||
self.room.send(ge.serialize()).unwrap();
|
||||
let events = self.scrape_until_gameeventtype(GameEventType::ElectionSet);
|
||||
if events.is_err() {
|
||||
return Err(Role::Null);
|
||||
}
|
||||
let events = events.unwrap();
|
||||
let chancellor_candidate_event = GameEvent::new(events.last().unwrap().clone());
|
||||
let chancellor_candidate = chancellor_candidate_event.targets();
|
||||
if chancellor_candidate.len() == 0 {
|
||||
debug!("no chancellor candidates found in election set");
|
||||
return self.game_election();
|
||||
}
|
||||
let chancellor_candidate = chancellor_candidate.last().unwrap();
|
||||
if self.player(chancellor_candidate.clone()).is_none() {
|
||||
debug!("invalid chancellor candidates found in election set");
|
||||
return self.game_election();
|
||||
}
|
||||
self.chancellor = Some(chancellor_candidate.clone());
|
||||
Ok(chancellor_candidate_event)
|
||||
}
|
||||
|
||||
pub fn game_election_vote(&mut self) -> Result<GameEvent, Role> {
|
||||
let mut votes: HashMap<String, bool> = HashMap::new();
|
||||
while votes.len() < self.players().len() {
|
||||
debug!("votes: {:?}", votes);
|
||||
let events = self.scrape_until_gameeventtype(GameEventType::VoteSet).unwrap();
|
||||
debug!("scrape until vote set found {:?}", events);
|
||||
if events.len() > 0 {
|
||||
let ge = GameEvent::new(events.last().unwrap().clone());
|
||||
let sources = ge.sources();
|
||||
if sources.len() > 0 {
|
||||
let player = sources.first().unwrap();
|
||||
if self.player(player.clone()).is_some() {
|
||||
let params = ge.params();
|
||||
if params.len() > 0 {
|
||||
votes.insert(player.clone(), params.first().unwrap() == "y");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("game election vote yielded: {:?}", votes);
|
||||
|
||||
let mut ge = GameEventType::VoteSet.build();
|
||||
ge.d["sources"] = json::array!["__gm__"];
|
||||
let mut yays = 0;
|
||||
for (_, vote) in &votes {
|
||||
if *vote {
|
||||
yays += 1;
|
||||
}
|
||||
}
|
||||
let gm_vote = yays > self.players().len() / 2;
|
||||
ge.d["params"] = json::array![format!("{:?}", gm_vote)];
|
||||
for (player, vote) in &votes {
|
||||
ge.d["sources"].push(player.clone());
|
||||
ge.d["params"].push(format!("{:?}", vote));
|
||||
}
|
||||
self.room.send(ge.serialize());
|
||||
|
||||
if ! gm_vote {
|
||||
self.president = None;
|
||||
self.chancellor = None;
|
||||
return Ok(GameEventType::VoteFailed.build());
|
||||
}
|
||||
|
||||
if self.game_is_over().is_err() {
|
||||
return Ok(GameEventType::GameStop.build());
|
||||
}
|
||||
Ok(GameEventType::Null.build())
|
||||
}
|
||||
|
||||
pub fn game_policy_select_random(&mut self) -> Result<GameEvent, Role> {
|
||||
self.failed_votes += 1;
|
||||
if self.failed_votes < 3 {
|
||||
return Ok(GameEventType::Null.build());
|
||||
}
|
||||
let policy = self.deck.pop().unwrap().clone();
|
||||
self.discard.push(policy.clone());
|
||||
let mut ge = GameEventType::PolicySet.build();
|
||||
ge.d["params"] = json::array![format!("{:?}", policy)];
|
||||
Ok(ge)
|
||||
}
|
||||
|
||||
pub fn game_policy_select(&mut self) -> Result<GameEvent, Role> {
|
||||
let mut draw = vec![];
|
||||
let mut to_discard = vec![];
|
||||
for _ in 0..3 {
|
||||
draw.push(self.deck.pop().unwrap().clone());
|
||||
to_discard.push(self.deck.pop().unwrap().clone());
|
||||
}
|
||||
|
||||
let mut ge = GameEventType::CardPend.build();
|
||||
if self.president.is_none() {
|
||||
return Err(Role::Null);
|
||||
}
|
||||
ge.d["targets"] = json::array![self.president.clone().unwrap().clone()];
|
||||
ge.d["params"] = json::array![];
|
||||
for policy in &draw {
|
||||
ge.d["params"].push(format!("{:?}", policy));
|
||||
}
|
||||
self.room.send(ge.serialize()).unwrap();
|
||||
|
||||
draw.clear();
|
||||
loop {
|
||||
let events = self.scrape_until_gameeventtype(GameEventType::CardPick);
|
||||
if events.is_err() {
|
||||
return Err(Role::Null);
|
||||
}
|
||||
let events = events.unwrap();
|
||||
if events.len() == 0 {
|
||||
return Err(Role::Null);
|
||||
}
|
||||
let card_pick_event = GameEvent::new(events.last().unwrap().clone());
|
||||
let sources = card_pick_event.sources();
|
||||
if sources.len() == 0 || sources.first().unwrap().clone() != self.president.clone().unwrap() {
|
||||
continue
|
||||
}
|
||||
let params = card_pick_event.params();
|
||||
if params.len() != 2 {
|
||||
continue
|
||||
}
|
||||
for param in ¶ms {
|
||||
let policy = Policy::from_string(param.clone());
|
||||
if policy != Policy::Null {
|
||||
draw.push(policy);
|
||||
}
|
||||
}
|
||||
if draw.len() != params.len() {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let mut ge = GameEventType::CardPend.build();
|
||||
if self.chancellor.is_none() {
|
||||
return Err(Role::Null);
|
||||
}
|
||||
ge.d["targets"] = json::array![self.chancellor.clone().unwrap().clone()];
|
||||
ge.d["params"] = json::array![];
|
||||
for policy in &draw {
|
||||
ge.d["params"].push(format!("{:?}", policy));
|
||||
}
|
||||
self.room.send(ge.serialize()).unwrap();
|
||||
|
||||
draw.clear();
|
||||
loop {
|
||||
let events = self.scrape_until_gameeventtype(GameEventType::CardPick);
|
||||
if events.is_err() {
|
||||
return Err(Role::Null);
|
||||
}
|
||||
let events = events.unwrap();
|
||||
if events.len() == 0 {
|
||||
return Err(Role::Null);
|
||||
}
|
||||
let card_pick_event = GameEvent::new(events.last().unwrap().clone());
|
||||
let sources = card_pick_event.sources();
|
||||
if sources.len() == 0 || sources.first().unwrap().clone() != self.chancellor.clone().unwrap() {
|
||||
continue
|
||||
}
|
||||
let params = card_pick_event.params();
|
||||
if params.len() != 1 {
|
||||
continue
|
||||
}
|
||||
for param in ¶ms {
|
||||
let policy = Policy::from_string(param.clone());
|
||||
if policy != Policy::Null {
|
||||
draw.push(policy);
|
||||
}
|
||||
}
|
||||
if draw.len() != params.len() {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let policy = draw[0].clone();
|
||||
for i in 0..to_discard.len() {
|
||||
if policy == to_discard[i] {
|
||||
to_discard.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if to_discard.len() == 3 {
|
||||
return Err(Role::Null);
|
||||
}
|
||||
for i in &to_discard {
|
||||
self.discard.push(i.clone());
|
||||
}
|
||||
|
||||
let mut ge = GameEventType::PolicySet.build();
|
||||
ge.d["params"] = json::array!(format!("{:?}", policy));
|
||||
Ok(ge)
|
||||
}
|
||||
|
||||
pub fn game_policy_veto(&mut self, _p: Policy) -> Result<GameEvent, Role> {
|
||||
Err(Role::Null)
|
||||
}
|
||||
|
||||
pub fn game_ends_with(&mut self, _p: Policy) -> Result<GameEvent, Role> {
|
||||
Err(Role::Null)
|
||||
}
|
||||
|
||||
pub fn game_policy_enact(&mut self, _p: Policy) -> Result<GameEvent, Role> {
|
||||
Err(Role::Null)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::super::super::super::model::state::mockrooms::MockRooms;
|
||||
use super::super::super::super::super::model::state::rooms::Rooms;
|
||||
use super::super::super::super::super::config;
|
||||
use super::super::super::player::Player;
|
||||
|
||||
use log::{debug, LevelFilter};
|
||||
use json;
|
||||
|
||||
fn init() {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter_level(LevelFilter::Trace)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
fn dummy() -> GameMaster {
|
||||
init();
|
||||
let mut mrs = MockRooms::new();
|
||||
let r = mrs.create("__gm__".to_string());
|
||||
let room_id = r.room_id().clone();
|
||||
let mut gm = GameMaster::new(r);
|
||||
for i in 0..config::MIN_PLAYERS-2 {
|
||||
assert!(mrs.join(i.to_string(), room_id.clone()).is_ok());
|
||||
}
|
||||
mrs.join((config::MIN_PLAYERS-1).to_string(), room_id).unwrap().send(format!(r#"{{
|
||||
"msgtype": "m.text",
|
||||
"body": "{{\"GameEventType\": \"GameStart\"}}"
|
||||
}}"#)).unwrap();
|
||||
assert!(gm.run_lobby().is_ok());
|
||||
assert!(gm.run_game_setup().is_ok());
|
||||
gm
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dummy() {
|
||||
let gm = dummy();
|
||||
assert!(gm.players().len() == config::MIN_PLAYERS);
|
||||
assert!(config::players_to_policies_facist(gm.players().len()) + config::players_to_policies_liberal(gm.players().len()) == gm.deck.len());
|
||||
assert!(gm.players().len() < gm.candidate_presidents.len());
|
||||
assert!(gm.policies[&Policy::Facist] == 0);
|
||||
assert!(gm.policies[&Policy::Liberal] == 0);
|
||||
let mut found = false;
|
||||
for i in 1..gm.candidate_presidents.len() {
|
||||
found = found || gm.candidate_presidents[i-1] > gm.candidate_presidents[i];
|
||||
}
|
||||
assert!(found);
|
||||
debug!("gamemaster dummy: {:?}", gm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_is_over() {
|
||||
let mut gm = dummy();
|
||||
assert!(gm.game_is_over().is_ok());
|
||||
|
||||
let mut gm = dummy();
|
||||
gm.policies.insert(Policy::Facist, 10);
|
||||
assert!(gm.game_is_over().is_err());
|
||||
assert!(gm.game_is_over().err().unwrap() == Role::Facist);
|
||||
|
||||
let mut gm = dummy();
|
||||
gm.policies.insert(Policy::Liberal, 10);
|
||||
assert!(gm.game_is_over().err().unwrap() == Role::Liberal);
|
||||
|
||||
let mut gm = dummy();
|
||||
gm.policies.insert(Policy::Facist, 3);
|
||||
assert!(gm.game_is_over().is_ok());
|
||||
gm.chancellor = Some("123".to_string());
|
||||
assert!(gm.game_is_over().is_ok());
|
||||
let mut p = Player::new("123".to_string());
|
||||
p.set_role(Role::Liberal);
|
||||
gm.lobby.players.insert("123".to_string(), p);
|
||||
assert!(gm.game_is_over().is_ok());
|
||||
|
||||
let mut gm = dummy();
|
||||
gm.policies.insert(Policy::Facist, 3);
|
||||
assert!(gm.game_is_over().is_ok());
|
||||
gm.chancellor = Some("123".to_string());
|
||||
assert!(gm.game_is_over().is_ok());
|
||||
let mut p = Player::new("123".to_string());
|
||||
p.set_role(Role::Facist);
|
||||
gm.lobby.players.insert("123".to_string(), p);
|
||||
assert!(gm.game_is_over().is_ok());
|
||||
|
||||
let mut gm = dummy();
|
||||
gm.policies.insert(Policy::Facist, 3);
|
||||
assert!(gm.game_is_over().is_ok());
|
||||
gm.chancellor = Some("123".to_string());
|
||||
assert!(gm.game_is_over().is_ok());
|
||||
let mut p = Player::new("123".to_string());
|
||||
p.set_role(Role::Hitler);
|
||||
gm.lobby.players.insert("123".to_string(), p);
|
||||
assert!(gm.game_is_over().is_err());
|
||||
assert!(gm.game_is_over().err().unwrap() == Role::Facist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_election() {
|
||||
let mut gm = dummy();
|
||||
|
||||
debug!("sending a null event");
|
||||
let ge = GameEventType::Null.build();
|
||||
assert!(ge.mode() == GameEventType::Null);
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending an empty election set event");
|
||||
let ge = GameEventType::ElectionSet.build();
|
||||
assert!(ge.mode() == GameEventType::ElectionSet);
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a params election set event");
|
||||
let mut ge = GameEventType::ElectionSet.build();
|
||||
assert!(ge.mode() == GameEventType::ElectionSet);
|
||||
ge.d["params"] = json::array!["a"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a sources election set event");
|
||||
let mut ge = GameEventType::ElectionSet.build();
|
||||
assert!(ge.mode() == GameEventType::ElectionSet);
|
||||
ge.d["sources"] = json::array!["b"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending an empty targets election set event");
|
||||
let mut ge = GameEventType::ElectionSet.build();
|
||||
assert!(ge.mode() == GameEventType::ElectionSet);
|
||||
ge.d["targets"] = json::array![];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending an invalid targets election set event");
|
||||
let mut ge = GameEventType::ElectionSet.build();
|
||||
assert!(ge.mode() == GameEventType::ElectionSet);
|
||||
ge.d["targets"] = json::array!["abc"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a valid targets election set event");
|
||||
let mut ge = GameEventType::ElectionSet.build();
|
||||
assert!(ge.mode() == GameEventType::ElectionSet);
|
||||
ge.d["targets"] = json::array!["realplayer"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
gm.lobby.players.insert("realplayer".to_string(), Player::new("realplayer".to_string()));
|
||||
|
||||
debug!("running game election");
|
||||
assert!(gm.game_election().is_ok());
|
||||
|
||||
let election_pends = gm.room.sync();
|
||||
assert!(election_pends.len() == 6, "election_pends: {:?}", election_pends);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_election_vote() {
|
||||
let mut gm = dummy();
|
||||
gm.lobby.players = HashMap::new();
|
||||
|
||||
debug!("sending a null event");
|
||||
let ge = GameEventType::Null.build();
|
||||
assert!(ge.mode() == GameEventType::Null);
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a valid vote from a valid player");
|
||||
let mut ge = GameEventType::VoteSet.build();
|
||||
assert!(ge.mode() == GameEventType::VoteSet);
|
||||
ge.d["sources"] = json::array!["1"];
|
||||
ge.d["params"] = json::array!["n"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
gm.lobby.players.insert("1".to_string(), Player::new("1".to_string()));
|
||||
|
||||
debug!("sending a valid repeat vote from a valid player");
|
||||
let mut ge = GameEventType::VoteSet.build();
|
||||
assert!(ge.mode() == GameEventType::VoteSet);
|
||||
ge.d["sources"] = json::array!["1"];
|
||||
ge.d["params"] = json::array!["y"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending an invalid vote from a valid player");
|
||||
let mut ge = GameEventType::VoteSet.build();
|
||||
assert!(ge.mode() == GameEventType::VoteSet);
|
||||
ge.d["sources"] = json::array![];
|
||||
ge.d["params"] = json::array!["n"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
gm.lobby.players.insert("2".to_string(), Player::new("2".to_string()));
|
||||
|
||||
debug!("correcting an invalid vote from a valid player");
|
||||
let mut ge = GameEventType::VoteSet.build();
|
||||
assert!(ge.mode() == GameEventType::VoteSet);
|
||||
ge.d["sources"] = json::array!["2"];
|
||||
ge.d["params"] = json::array!["y"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
let ge = gm.game_election_vote();
|
||||
debug!("game election vote result: {:?}", ge);
|
||||
assert!(ge.is_ok());
|
||||
|
||||
let events = gm.room.sync();
|
||||
assert!(events.len() == 1);
|
||||
let e = events.last().unwrap().clone();
|
||||
let ge = GameEvent::new(e.clone());
|
||||
assert!(ge.sender == "__gm__", "post-game election vote ge: {:?} from {:?}", ge, e);
|
||||
assert!(ge.mode() == GameEventType::VoteSet);
|
||||
assert!(ge.sources()[0] == "__gm__");
|
||||
assert!(ge.params()[0] == "true");
|
||||
assert!(ge.params()[1] == "true");
|
||||
assert!(ge.params()[2] == "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_policy_select() {
|
||||
let mut gm = dummy();
|
||||
gm.president = Some("president".to_string());
|
||||
gm.chancellor = Some("chancellor".to_string());
|
||||
|
||||
debug!("sending an irrelevant event to pres picking a policy");
|
||||
let mut ge = GameEventType::Null.build();
|
||||
assert!(ge.mode() == GameEventType::Null);
|
||||
ge.d["sources"] = json::array!["2"];
|
||||
ge.d["params"] = json::array!["y"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a invalid president card pick");
|
||||
let mut ge = GameEventType::CardPick.build();
|
||||
assert!(ge.mode() == GameEventType::CardPick);
|
||||
ge.d["sources"] = json::array!["president"];
|
||||
ge.d["params"] = json::array!["abc"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a short president card pick");
|
||||
let mut ge = GameEventType::CardPick.build();
|
||||
assert!(ge.mode() == GameEventType::CardPick);
|
||||
ge.d["sources"] = json::array!["president"];
|
||||
ge.d["params"] = json::array!["Facist"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a valid president card pick");
|
||||
let mut ge = GameEventType::CardPick.build();
|
||||
assert!(ge.mode() == GameEventType::CardPick);
|
||||
ge.d["sources"] = json::array!["president"];
|
||||
ge.d["params"] = json::array!["Facist", "Liberal"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending an irrelevant event to chan picking a policy");
|
||||
let mut ge = GameEventType::Null.build();
|
||||
assert!(ge.mode() == GameEventType::Null);
|
||||
ge.d["sources"] = json::array!["2"];
|
||||
ge.d["params"] = json::array!["y"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a invalid chan card pick");
|
||||
let mut ge = GameEventType::CardPick.build();
|
||||
assert!(ge.mode() == GameEventType::CardPick);
|
||||
ge.d["sources"] = json::array!["chancellor"];
|
||||
ge.d["params"] = json::array!["abc"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a short chan card pick");
|
||||
let mut ge = GameEventType::CardPick.build();
|
||||
assert!(ge.mode() == GameEventType::CardPick);
|
||||
ge.d["sources"] = json::array!["chancellor"];
|
||||
ge.d["params"] = json::array![];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
debug!("sending a valid chan card pick");
|
||||
let mut ge = GameEventType::CardPick.build();
|
||||
assert!(ge.mode() == GameEventType::CardPick);
|
||||
ge.d["sources"] = json::array!["chancellor"];
|
||||
ge.d["params"] = json::array!["Facist"];
|
||||
assert!(gm.room.send(ge.serialize()).is_ok());
|
||||
debug!("sent: {}", ge.serialize());
|
||||
|
||||
let ge = gm.game_policy_select();
|
||||
assert!(ge.is_ok());
|
||||
let ge = ge.unwrap();
|
||||
assert!(ge.mode() == GameEventType::PolicySet);
|
||||
assert!(ge.params().len() == 1);
|
||||
assert!(ge.params().first().unwrap() == "Facist");
|
||||
assert!(gm.discard.len() == 2);
|
||||
// todo assert cards picked were viable
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_policy_select_random() {
|
||||
let mut gm = dummy();
|
||||
gm.failed_votes = 0;
|
||||
let ge = gm.game_policy_select_random();
|
||||
assert!(ge.is_ok());
|
||||
assert!(ge.unwrap().mode() == GameEventType::Null);
|
||||
assert!(gm.discard.len() == 0);
|
||||
|
||||
let mut gm = dummy();
|
||||
gm.failed_votes = 2;
|
||||
let ge = gm.game_policy_select_random();
|
||||
assert!(ge.is_ok());
|
||||
let ge = ge.unwrap();
|
||||
assert!(ge.clone().mode() == GameEventType::PolicySet);
|
||||
assert!(ge.clone().params().len() == 1);
|
||||
let policy = Policy::from_string(ge.clone().params().first().clone().unwrap().clone());
|
||||
assert!(policy != Policy::Null);
|
||||
assert!(gm.discard.len() == 1);
|
||||
assert!(gm.discard.first().unwrap().clone() == policy);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
use super::super::super::super::model::state::room::Room;
|
||||
use super::super::super::super::model::state::event::Event;
|
||||
use super::super::lobby::Lobby;
|
||||
use super::super::player::Player;
|
||||
use super::super::policy::Policy;
|
||||
use super::super::role::Role;
|
||||
use super::super::gameevent::GameEventType;
|
||||
use super::super::gameevent::GameEvent;
|
||||
|
||||
use log::{info, debug, error};
|
||||
use std::collections::HashMap;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GameMaster {
|
||||
pub room: Box<dyn Room>,
|
||||
pub lobby: Lobby,
|
||||
pub candidate_presidents: Vec<String>,
|
||||
pub deck: Vec<Policy>,
|
||||
pub discard: Vec<Policy>,
|
||||
pub policies: HashMap<Policy, usize>,
|
||||
pub president: Option<String>,
|
||||
pub chancellor: Option<String>,
|
||||
pub failed_votes: usize,
|
||||
}
|
||||
|
||||
impl GameMaster {
|
||||
pub fn new(room: Box<dyn Room>) -> GameMaster {
|
||||
info!("created for room {}", room.room_id());
|
||||
GameMaster{
|
||||
room: room,
|
||||
lobby: Lobby::new(),
|
||||
candidate_presidents: vec![],
|
||||
deck: vec![],
|
||||
discard: vec![],
|
||||
policies: HashMap::new(),
|
||||
president: None,
|
||||
chancellor: None,
|
||||
failed_votes: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<GameEvent, Role> {
|
||||
loop {
|
||||
let r = self.run_lobby();
|
||||
if r.is_ok() {
|
||||
break
|
||||
}
|
||||
error!("error running lobby: {:?}", r);
|
||||
}
|
||||
self.run_game_setup()?;
|
||||
self.run_game()
|
||||
}
|
||||
|
||||
pub fn players(&self) -> Vec<String> {
|
||||
let mut players = vec![];
|
||||
for k in self.lobby.players.keys() {
|
||||
players.push(k.clone());
|
||||
}
|
||||
players
|
||||
}
|
||||
|
||||
pub fn player(&mut self, id: String) -> Option<&mut Player> {
|
||||
self.lobby.players.get_mut(&id)
|
||||
}
|
||||
|
||||
pub fn scrape_until_gameeventtype(&mut self, get: GameEventType) -> Result<Vec<Event>, String> {
|
||||
let mut scraped = vec![];
|
||||
loop {
|
||||
let events = self.room.sync();
|
||||
for e in &events {
|
||||
scraped.push(e.clone());
|
||||
let ge = GameEvent::new(e.clone());
|
||||
debug!("scrape_until {:?}: {:?}: ge: {:?}", get, ge.mode() == get, ge);
|
||||
if ge.mode() == get {
|
||||
debug!("/scrape_until {:?}: {:?}: ge: {:?}", get, ge.mode() == get, ge);
|
||||
self.room.rollback(e.since.clone());
|
||||
return Ok(scraped);
|
||||
}
|
||||
}
|
||||
thread::sleep(time::Duration::new(1, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::super::super::super::model::state::mockroom::MockRoom;
|
||||
use super::super::super::super::super::model::state::mockrooms::MockRooms;
|
||||
use super::super::super::super::super::model::state::rooms::Rooms;
|
||||
|
||||
#[test]
|
||||
fn new_mockroom() {
|
||||
let _ = GameMaster::new(Box::new(MockRoom::create("__gm__".to_string())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_rooms_mockrooms() {
|
||||
fn get() -> impl Rooms {
|
||||
let mrs = MockRooms::new();
|
||||
mrs
|
||||
}
|
||||
let mut mrs = get();
|
||||
let r = mrs.create("__gm__".to_string());
|
||||
let _ = GameMaster::new(r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_mockrooms() {
|
||||
let mut mrs = MockRooms::new();
|
||||
let r = mrs.create("__gm__".to_string());
|
||||
let _ = GameMaster::new(r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scrape_until_get() {
|
||||
let mut mrs = MockRooms::new();
|
||||
let r = mrs.create("__gm__".to_string());
|
||||
let room_id = r.room_id().clone();
|
||||
let r2 = mrs.join("r2".to_string(), room_id);
|
||||
assert!(r2.is_ok());
|
||||
let mut r2 = r2.unwrap();
|
||||
let mut gm = GameMaster::new(r);
|
||||
gm.room.sync();
|
||||
r2.send(GameEventType::Null.build().serialize()).unwrap();
|
||||
let scraped = gm.scrape_until_gameeventtype(GameEventType::Null);
|
||||
assert!(scraped.is_ok());
|
||||
let scraped = scraped.unwrap();
|
||||
assert!(scraped.len() == 1);
|
||||
assert!(scraped.first().is_some());
|
||||
let e = scraped.first().unwrap();
|
||||
assert!(GameEvent::new(e.clone()).mode() == GameEventType::Null, "{:?}", e);
|
||||
assert!(gm.room.sync().len() == 0);
|
||||
|
||||
r2.send(GameEventType::ElectionSet.build().serialize()).unwrap();
|
||||
r2.send(GameEventType::ElectionSet.build().serialize()).unwrap();
|
||||
let scraped = gm.scrape_until_gameeventtype(GameEventType::ElectionSet);
|
||||
assert!(scraped.is_ok());
|
||||
let scraped = scraped.unwrap();
|
||||
assert!(scraped.len() == 1);
|
||||
assert!(gm.room.sync().len() == 1);
|
||||
|
||||
r2.send(GameEventType::SpecialPeek.build().serialize()).unwrap();
|
||||
r2.send(GameEventType::ElectionPend.build().serialize()).unwrap();
|
||||
let scraped = gm.scrape_until_gameeventtype(GameEventType::ElectionPend);
|
||||
assert!(scraped.is_ok());
|
||||
let scraped = scraped.unwrap();
|
||||
assert!(scraped.len() == 2);
|
||||
assert!(gm.room.sync().len() == 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
use super::gamemaster::GameMaster;
|
||||
|
||||
use super::super::gameevent::GameEvent;
|
||||
use super::super::gameevent::GameEventType;
|
||||
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
impl GameMaster {
|
||||
pub fn run_lobby(&mut self) -> Result<usize, String> {
|
||||
loop {
|
||||
let r = self.run_lobby_scrape().clone();
|
||||
if r.clone().is_err() {
|
||||
return r;
|
||||
}
|
||||
if r.clone().unwrap_or(0) != 0 {
|
||||
return r;
|
||||
}
|
||||
thread::sleep(time::Duration::new(1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_lobby_scrape(&mut self) -> Result<usize, String> {
|
||||
let events = self.scrape_until_gameeventtype(GameEventType::GameStart)?;
|
||||
for e in &events {
|
||||
self.lobby.eat(e.clone());
|
||||
}
|
||||
if events.len() > 0 {
|
||||
let last = events.last().unwrap();
|
||||
let ge = GameEvent::new(last.clone());
|
||||
if ge.mode() == GameEventType::GameStart {
|
||||
self.lobby.lock();
|
||||
}
|
||||
}
|
||||
return self.lobby.ready();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::super::super::super::model::state::mockrooms::MockRooms;
|
||||
use super::super::super::super::super::model::state::rooms::Rooms;
|
||||
use super::super::super::super::super::config;
|
||||
use log::{LevelFilter};
|
||||
|
||||
fn init() {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter_level(LevelFilter::Trace)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_lobby() {
|
||||
init();
|
||||
let mut mrs = MockRooms::new();
|
||||
let r1 = mrs.create("__gm__".to_string());
|
||||
let room_id = r1.room_id();
|
||||
let mut gm = GameMaster::new(r1);
|
||||
for i in 0..config::MIN_PLAYERS-1 {
|
||||
let mut r2 = mrs.join(i.to_string(), room_id.clone()).unwrap();
|
||||
r2.send(format!(r#"{{
|
||||
"msgtype": "m.text",
|
||||
"body": "{{\"GameEventType\": \"GameStart\"}}"
|
||||
}}"#)).unwrap();
|
||||
let ready = gm.run_lobby();
|
||||
assert!(ready.is_err() == (i != 3), "want {:?} for ready.is_err #{:?}, which is {:?}", i != 3, i, ready.is_err());
|
||||
}
|
||||
assert!(gm.lobby.players.len() == 5, "first run_lobby players: {:?}, sync: {:?}", gm.lobby.players, gm.room.sync());
|
||||
let mut players1 = vec![];
|
||||
for k in gm.lobby.players.keys() {
|
||||
players1.push(k.clone());
|
||||
}
|
||||
let mut r2 = mrs.join("r2".to_string(), room_id.clone()).unwrap();
|
||||
r2.send(format!(r#"{{
|
||||
"msgtype": "m.text",
|
||||
"body": "{{\"GameEventType\": \"GameStart\"}}"
|
||||
}}"#)).unwrap();
|
||||
assert!(gm.run_lobby().is_ok());
|
||||
assert!(gm.lobby.players.len() == 5, "secnd run_lobby players: {:?}, sync: {:?}", gm.lobby.players, gm.room.sync());
|
||||
let players2 = gm.lobby.players.keys();
|
||||
assert!(format!("{:?}", players1) == format!("{:?}", players2));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
pub mod gamemaster;
|
||||
mod lobby;
|
||||
mod setup;
|
||||
mod game;
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
use super::gamemaster::GameMaster;
|
||||
|
||||
use super::super::super::super::config;
|
||||
use super::super::role::Role;
|
||||
use super::super::policy::Policy;
|
||||
use super::super::rand::rand_usize;
|
||||
use super::super::rand::shuffle;
|
||||
|
||||
use log::{debug, error, LevelFilter};
|
||||
|
||||
impl GameMaster {
|
||||
pub fn run_game_setup(&mut self) -> Result<String, Role> {
|
||||
self.setup_gather_candidates()?;
|
||||
debug!("players: {:?}", self.candidate_presidents);
|
||||
self.setup_set_roles()?;
|
||||
debug!("/players: {:?}", self.candidate_presidents);
|
||||
self.setup_order_candidates()?;
|
||||
debug!("/players: {:?}", self.candidate_presidents);
|
||||
self.setup_deck()?;
|
||||
Ok("ok".to_string())
|
||||
}
|
||||
|
||||
pub fn setup_gather_candidates(&mut self) -> Result<String, Role> {
|
||||
for player in self.players() {
|
||||
let p = self.player(player.clone());
|
||||
if p.is_none() {
|
||||
error!("missing player {}", player);
|
||||
return Err(Role::Null);
|
||||
}
|
||||
self.candidate_presidents.push(player.clone());
|
||||
debug!("player = {}", player);
|
||||
}
|
||||
Ok("ok".to_string())
|
||||
}
|
||||
|
||||
pub fn setup_set_roles(&mut self) -> Result<String, Role> {
|
||||
for player in self.players() {
|
||||
self.player(player.clone()).unwrap().set_role(Role::Liberal);
|
||||
}
|
||||
let n = config::players_to_facists(self.players().len());
|
||||
if n.is_err() {
|
||||
return Err(Role::Null);
|
||||
}
|
||||
let n = n.unwrap();
|
||||
for i in 0..n {
|
||||
debug!("picking facist {}/{} for {} players", i, n, self.players().len());
|
||||
loop {
|
||||
let j = rand_usize(self.players().len());
|
||||
let id = self.candidate_presidents[j].clone();
|
||||
let player = self.player(id.clone()).unwrap();
|
||||
if player.get_role() != Role::Liberal {
|
||||
continue;
|
||||
}
|
||||
let role = match i {
|
||||
0 => Role::Hitler,
|
||||
_ => Role::Facist,
|
||||
};
|
||||
player.set_role(role);
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.president = None;
|
||||
self.chancellor = None;
|
||||
Ok("ok".to_string())
|
||||
}
|
||||
|
||||
pub fn setup_order_candidates(&mut self) -> Result<String, Role> {
|
||||
shuffle(&mut self.candidate_presidents);
|
||||
let n = self.candidate_presidents.len();
|
||||
for _ in 0..5 {
|
||||
for i in 0..n {
|
||||
self.candidate_presidents.push(self.candidate_presidents[i].clone());
|
||||
}
|
||||
}
|
||||
Ok("ok".to_string())
|
||||
}
|
||||
|
||||
pub fn setup_deck(&mut self) -> Result<String, Role> {
|
||||
for _ in 0..config::players_to_policies_facist(self.players().len()) {
|
||||
self.deck.push(Policy::Facist);
|
||||
}
|
||||
|
||||
for _ in 0..config::players_to_policies_liberal(self.players().len()) {
|
||||
self.deck.push(Policy::Liberal);
|
||||
}
|
||||
|
||||
shuffle(&mut self.deck);
|
||||
|
||||
self.policies.insert(Policy::Facist, 0); // todo start iwth 1 if 5 players?
|
||||
self.policies.insert(Policy::Liberal, 0); // todo start iwth 1 if 5 players?
|
||||
|
||||
Ok("deck is loaded".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::super::super::super::model::state::mockrooms::MockRooms;
|
||||
use super::super::super::super::super::model::state::rooms::Rooms;
|
||||
use super::super::super::player::Player;
|
||||
|
||||
fn init() {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter_level(LevelFilter::Trace)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_game_setup() {
|
||||
init();
|
||||
let mut mrs = MockRooms::new();
|
||||
let r1 = mrs.create("__gm__".to_string());
|
||||
let mut gm = GameMaster::new(r1);
|
||||
let r = gm.run_game_setup();
|
||||
assert!(!r.is_ok());
|
||||
for i in 0..config::MIN_PLAYERS {
|
||||
let id = format!("{}", i);
|
||||
gm.lobby.players.insert(id.clone(), Player::new(id.clone()));
|
||||
}
|
||||
let r = gm.run_game_setup();
|
||||
assert!(r.is_ok(), "failed to start game after sufficient players joined: {:?}", r);
|
||||
assert!(gm.candidate_presidents.len() > gm.lobby.players.len());
|
||||
assert!(gm.deck.len() > 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setup_gather_candidates() {
|
||||
init();
|
||||
let mut mrs = MockRooms::new();
|
||||
let r1 = mrs.create("__gm__".to_string());
|
||||
let mut gm = GameMaster::new(r1);
|
||||
assert!(gm.setup_gather_candidates().is_ok());
|
||||
for i in 0..config::MIN_PLAYERS {
|
||||
let id = format!("{}", i);
|
||||
gm.lobby.players.insert(id.clone(), Player::new(id.clone()));
|
||||
}
|
||||
assert!(gm.setup_gather_candidates().is_ok());
|
||||
for i in config::MIN_PLAYERS..config::MAX_PLAYERS+1 {
|
||||
let id = format!("{}", i);
|
||||
gm.lobby.players.insert(id.clone(), Player::new(id.clone()));
|
||||
}
|
||||
assert!(gm.setup_gather_candidates().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setup_set_roles() {
|
||||
init();
|
||||
let mut mrs = MockRooms::new();
|
||||
let r1 = mrs.create("__gm__".to_string());
|
||||
let mut gm = GameMaster::new(r1);
|
||||
assert!(gm.setup_set_roles().is_err());
|
||||
for i in 0..config::MIN_PLAYERS {
|
||||
let id = format!("{}", i);
|
||||
gm.lobby.players.insert(id.clone(), Player::new(id.clone()));
|
||||
gm.candidate_presidents.push(id.clone());
|
||||
}
|
||||
assert!(gm.setup_set_roles().is_ok());
|
||||
for i in config::MIN_PLAYERS..config::MAX_PLAYERS+1 {
|
||||
let id = format!("{}", i);
|
||||
gm.lobby.players.insert(id.clone(), Player::new(id.clone()));
|
||||
gm.candidate_presidents.push(id.clone());
|
||||
}
|
||||
assert!(gm.setup_set_roles().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setup_order_candidates() {
|
||||
init();
|
||||
let mut mrs = MockRooms::new();
|
||||
let r1 = mrs.create("__gm__".to_string());
|
||||
let mut gm = GameMaster::new(r1);
|
||||
assert!(gm.setup_order_candidates().is_ok());
|
||||
gm.candidate_presidents = ["1".to_string()].to_vec();
|
||||
assert!(gm.setup_order_candidates().is_ok());
|
||||
assert!(gm.candidate_presidents.len() > 1);
|
||||
gm.candidate_presidents = [].to_vec();
|
||||
for i in 0..50 {
|
||||
gm.candidate_presidents.push(format!("{}", i));
|
||||
}
|
||||
let was = format!("{:?}", gm.candidate_presidents);
|
||||
assert!(gm.setup_order_candidates().is_ok());
|
||||
assert!(gm.candidate_presidents.len() > 50);
|
||||
assert!(was != format!("{:?}", gm.candidate_presidents));
|
||||
let mut found = false;
|
||||
for i in 1..gm.candidate_presidents.len() {
|
||||
found = found || gm.candidate_presidents[i] < gm.candidate_presidents[i-1];
|
||||
}
|
||||
assert!(found);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
use super::player::Player;
|
||||
use super::super::super::model::state::event::Event;
|
||||
use super::super::super::config;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Lobby {
|
||||
pub players: HashMap<String, Player>,
|
||||
pub locked: bool,
|
||||
}
|
||||
|
||||
impl Lobby {
|
||||
pub fn new() -> Lobby {
|
||||
Lobby{
|
||||
players: HashMap::new(),
|
||||
locked: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eat(&mut self, message: Event) {
|
||||
if self.locked {
|
||||
return;
|
||||
}
|
||||
let j = message.join();
|
||||
if j.is_none() {
|
||||
return;
|
||||
}
|
||||
let id = j.unwrap();
|
||||
self.players.insert(id.clone(), Player::new(id));
|
||||
}
|
||||
|
||||
pub fn lock(&mut self) {
|
||||
if self.ready().is_err() {
|
||||
return;
|
||||
}
|
||||
self.locked = true;
|
||||
}
|
||||
|
||||
pub fn ready(&self) -> Result<usize, String> {
|
||||
let n: usize = self.players.len();
|
||||
if n < config::MIN_PLAYERS {
|
||||
return Err("not enough players".to_string());
|
||||
}
|
||||
if n > config::MAX_PLAYERS {
|
||||
return Err("too many players".to_string());
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn _dummy_event(m: &str) -> Event {
|
||||
Event{
|
||||
body: m.to_string(),
|
||||
since: "a".to_string(),
|
||||
sender: "b".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_lobby() {
|
||||
let _ = Lobby::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eat_join() {
|
||||
let mut l = Lobby::new();
|
||||
let e = _dummy_event(r#"{"membership": "join", "displayname": "a"}"#);
|
||||
let was = l.players.len();
|
||||
l.eat(e);
|
||||
assert!(was+1 == l.players.len(), "want {}, got {}: {:?}", was+1, l.players.len(), l.players);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eat_join_malformatted() {
|
||||
let mut l = Lobby::new();
|
||||
let e = _dummy_event(r#"{"membership": "join"}"#);
|
||||
let was = l.players.len();
|
||||
l.eat(e);
|
||||
assert!(was == l.players.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eat_null() {
|
||||
let mut l = Lobby::new();
|
||||
let e = _dummy_event(r#"{"hello": "world"}"#);
|
||||
let was = l.players.len();
|
||||
l.eat(e);
|
||||
assert!(was == l.players.len());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
pub mod gamemaster;
|
||||
pub mod player;
|
||||
pub mod role;
|
||||
pub mod lobby;
|
||||
pub mod gameevent;
|
||||
pub mod policy;
|
||||
pub mod rand;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
use super::role::Role;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Player {
|
||||
id: String,
|
||||
role: Role,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(id: String) -> Player {
|
||||
Player {
|
||||
id: id,
|
||||
role: Role::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_role(&self) -> Role {
|
||||
self.role.clone()
|
||||
}
|
||||
|
||||
pub fn set_role(&mut self, role: Role) {
|
||||
self.role.set(role);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn new_player() {
|
||||
let _ = Player::new("id".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_role() {
|
||||
let mut p = Player::new("id".to_string());
|
||||
p.set_role(Role::Facist);
|
||||
assert!(p.role == Role::Facist);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Policy {
|
||||
Null,
|
||||
Facist,
|
||||
Liberal,
|
||||
}
|
||||
|
||||
impl Policy {
|
||||
pub fn from_string(s: String) -> Policy {
|
||||
let cases = [
|
||||
Policy::Null,
|
||||
Policy::Facist,
|
||||
Policy::Liberal,
|
||||
];
|
||||
for c in &cases {
|
||||
if format!("{:?}", c) == s {
|
||||
return c.clone();
|
||||
}
|
||||
}
|
||||
Policy::Null
|
||||
}
|
||||
|
||||
pub fn new() -> Policy {
|
||||
Policy::Null
|
||||
}
|
||||
|
||||
pub fn set(&mut self, policy: Policy) {
|
||||
*self = policy.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn new_role() {
|
||||
let _ = Policy::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set() {
|
||||
let mut r = Policy::new();
|
||||
r.set(Policy::Facist);
|
||||
assert!(r == Policy::Facist);
|
||||
|
||||
let mut r = Policy::new();
|
||||
r.set(Policy::Liberal);
|
||||
assert!(r == Policy::Liberal);
|
||||
}
|
||||
|
||||
fn from_string() {
|
||||
assert!(Policy::from_string("".to_string()) == Policy::Null);
|
||||
assert!(Policy::from_string("Liberal".to_string()) == Policy::Liberal);
|
||||
assert!(Policy::from_string("Facist".to_string()) == Policy::Facist);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
use rand::{self, Rng};
|
||||
|
||||
pub fn shuffle<T: Clone>(v: &mut Vec<T>) {
|
||||
for _ in 0..v.len()*2 {
|
||||
let a = rand_usize(v.len());
|
||||
let b = rand_usize(v.len());
|
||||
let t: T = v[a].clone();
|
||||
v[a] = v[b].clone();
|
||||
v[b] = t.clone();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rand_usize(n: usize) -> usize {
|
||||
rand::thread_rng().gen_range(0, n)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn _rand_usize() {
|
||||
let mut unique: HashMap<usize, bool> = HashMap::new();
|
||||
for _ in 0..100 {
|
||||
unique.insert(rand_usize(100), true);
|
||||
}
|
||||
assert!(unique.len() > 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn _shuffle() {
|
||||
let mut items: Vec<usize> = vec![];
|
||||
let n: usize = 50;
|
||||
for i in 0..n {
|
||||
items.push(i);
|
||||
}
|
||||
assert!(items.len() == n);
|
||||
shuffle(&mut items);
|
||||
assert!(items.len() == n);
|
||||
let mut found = false;
|
||||
for i in 1..items.len() {
|
||||
found = found || items[i] < items[i-1];
|
||||
}
|
||||
assert!(found);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Role {
|
||||
Null,
|
||||
Facist,
|
||||
Hitler,
|
||||
Liberal,
|
||||
}
|
||||
|
||||
impl Role {
|
||||
pub fn new() -> Role {
|
||||
Role::Null
|
||||
}
|
||||
|
||||
pub fn set(&mut self, role: Role) {
|
||||
*self = role.clone()
|
||||
}
|
||||
|
||||
pub fn is_hitler(&self) -> bool {
|
||||
self == &Role::Hitler
|
||||
}
|
||||
|
||||
pub fn is_facist(&self) -> bool {
|
||||
self == &Role::Facist || self.is_hitler()
|
||||
}
|
||||
|
||||
pub fn is_liberal(&self) -> bool {
|
||||
self == &Role::Liberal
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn new_role() {
|
||||
let _ = Role::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Facist);
|
||||
assert!(r == Role::Facist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_hitler_liberal() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Liberal);
|
||||
assert!(!r.is_hitler());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_hitler_facist() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Facist);
|
||||
assert!(!r.is_hitler());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_hitler_yes() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Hitler);
|
||||
assert!(r.is_hitler());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_facist_liberal() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Liberal);
|
||||
assert!(!r.is_facist());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_facist_facist() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Facist);
|
||||
assert!(r.is_facist());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_facist_hitler() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Hitler);
|
||||
assert!(r.is_facist());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_liberal_liberal() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Liberal);
|
||||
assert!(r.is_liberal());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_liberal_facist() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Facist);
|
||||
assert!(!r.is_liberal());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_liberal_hitler() {
|
||||
let mut r = Role::new();
|
||||
r.set(Role::Hitler);
|
||||
assert!(!r.is_liberal());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
pub mod gamemaster;
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
mod config;
|
||||
mod controller;
|
||||
mod model;
|
||||
mod view;
|
||||
|
||||
|
||||
use env_logger;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
println!("{}", config::PORT);
|
||||
/*
|
||||
use self::model::state::rooms::Rooms;
|
||||
let mut rooms = get_rooms();
|
||||
let room = rooms.create();
|
||||
let mut gamemaster = controller::gamemaster::gamemaster::GameMaster::new(room);
|
||||
let _ = gamemaster;
|
||||
}
|
||||
|
||||
fn get_rooms() -> impl Rooms {
|
||||
model::state::mockrooms::MockRooms::new()
|
||||
*/
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
pub mod state;
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
use json;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Event {
|
||||
pub sender: String,
|
||||
pub body: String,
|
||||
pub since: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum EventType {
|
||||
Null,
|
||||
Join,
|
||||
Create,
|
||||
Message,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn mode(&self) -> EventType {
|
||||
let d = json::parse(&self.body).unwrap();
|
||||
if d["membership"].is_string() {
|
||||
return match d["membership"].as_str().unwrap().to_string().as_ref() {
|
||||
"join" => EventType::Join,
|
||||
_ => EventType::Null,
|
||||
};
|
||||
}
|
||||
if d["creator"].is_string() {
|
||||
return EventType::Create;
|
||||
}
|
||||
if d["msgtype"].is_string() {
|
||||
return match d["msgtype"].as_str().unwrap().to_string().as_ref() {
|
||||
"m.text" => EventType::Message,
|
||||
_ => EventType::Null,
|
||||
};
|
||||
}
|
||||
EventType::Null
|
||||
}
|
||||
|
||||
pub fn join(&self) -> Option<String> {
|
||||
let d = json::parse(&self.body).unwrap();
|
||||
match self.mode() {
|
||||
EventType::Join => true,
|
||||
_ => return None,
|
||||
};
|
||||
let o = d["displayname"].as_str();
|
||||
if o.is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(o.unwrap().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn _dummy() -> Event {
|
||||
Event{
|
||||
sender: "sender".to_string(),
|
||||
body: r#"{}"#.to_string(),
|
||||
since: "since".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event() {
|
||||
let e = Event{
|
||||
sender: "sender".to_string(),
|
||||
body: "body".to_string(),
|
||||
since: "since".to_string(),
|
||||
};
|
||||
println!("{:?}", e);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_null() {
|
||||
let mut e = _dummy();
|
||||
e.body = r#"{
|
||||
}"#.to_string();
|
||||
assert!(e.mode() == EventType::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_create() {
|
||||
let mut e = _dummy();
|
||||
e.body = r#"{
|
||||
"creator": "abc"
|
||||
}"#.to_string();
|
||||
assert!(e.mode() == EventType::Create);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_msgtype() {
|
||||
let mut e = _dummy();
|
||||
e.body = r#"{
|
||||
"msgtype": "m.text"
|
||||
}"#.to_string();
|
||||
assert!(e.mode() == EventType::Message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_join() {
|
||||
let mut e = _dummy();
|
||||
e.body = r#"{
|
||||
"membership": "join"
|
||||
}"#.to_string();
|
||||
assert!(e.mode() == EventType::Join);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_some() {
|
||||
let mut e = _dummy();
|
||||
e.body = r#"{
|
||||
"displayname": "hi",
|
||||
"membership": "join"
|
||||
}"#.to_string();
|
||||
let j = e.join();
|
||||
assert!(j.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_some_bad() {
|
||||
let mut e = _dummy();
|
||||
e.body = r#"{
|
||||
"membership": "join"
|
||||
}"#.to_string();
|
||||
let j = e.join();
|
||||
assert!(j.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_none() {
|
||||
let mut e = _dummy();
|
||||
e.body = r#"{
|
||||
"a": "b"
|
||||
}"#.to_string();
|
||||
let j = e.join();
|
||||
assert!(j.is_none());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
use super::room::Room;
|
||||
use super::event::Event;
|
||||
|
||||
use rand::{self, Rng};
|
||||
use rand::distributions::Alphanumeric;
|
||||
use crossbeam_channel::{unbounded, Sender, Receiver};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MockRoom {
|
||||
since: String,
|
||||
room_id: String,
|
||||
events_s: Sender<Vec<Event>>,
|
||||
events_r: Receiver<Vec<Event>>,
|
||||
pub sender: String,
|
||||
}
|
||||
|
||||
impl MockRoom {
|
||||
pub fn create(sender: String) -> MockRoom {
|
||||
MockRoom::join(sender, rands())
|
||||
}
|
||||
|
||||
pub fn join(sender: String, room_id: String) -> MockRoom {
|
||||
let (s, r) = unbounded();
|
||||
s.send(vec![]).ok().unwrap();
|
||||
let mut mr = MockRoom {
|
||||
since: "".to_string(),
|
||||
room_id: room_id.clone(),
|
||||
events_s: s,
|
||||
events_r: r,
|
||||
sender: sender,
|
||||
};
|
||||
mr.send_as(mr.sender.clone(), format!(r#"{{
|
||||
"displayname": "{}",
|
||||
"membership": "join"
|
||||
}}"#, mr.sender.clone())).unwrap();
|
||||
mr
|
||||
}
|
||||
|
||||
pub fn room(&self) -> impl Room {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
pub fn send_as(&mut self, id: String, message: String) -> Result<String, &str> {
|
||||
let since = rands();
|
||||
let e = Event{
|
||||
sender: id,
|
||||
since: since.clone(),
|
||||
body: message,
|
||||
};
|
||||
let mut events = self.events_r.recv().ok().unwrap();
|
||||
events.push(e);
|
||||
self.events_s.send(events).ok().unwrap();
|
||||
Ok(since.clone())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Room for MockRoom {
|
||||
fn sync(&mut self) -> Vec<Event> {
|
||||
let mut unseen: Vec<Event> = vec![];
|
||||
let mut since = self.since.clone();
|
||||
let events = self.events_r.recv().ok().unwrap();
|
||||
for e in &events {
|
||||
if e.since == self.since {
|
||||
unseen.clear();
|
||||
since = self.since.clone();
|
||||
} else {
|
||||
unseen.push(e.clone());
|
||||
since = e.since.clone();
|
||||
}
|
||||
}
|
||||
self.events_s.send(events).ok().unwrap();
|
||||
self.since = since;
|
||||
return unseen;
|
||||
}
|
||||
|
||||
fn send(&mut self, message: String) -> Result<String, &str> {
|
||||
self.send_as(self.sender.clone(), message)
|
||||
}
|
||||
|
||||
fn room_id(&self) -> String {
|
||||
self.room_id.clone()
|
||||
}
|
||||
|
||||
fn rollback(&mut self, since: String) {
|
||||
self.since = since;
|
||||
}
|
||||
|
||||
fn since(&self) -> String {
|
||||
self.since.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MockRoom {
|
||||
fn drop(&mut self) {
|
||||
println!("MockRoom::drop not impl");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rands() -> String {
|
||||
rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(10)
|
||||
.collect::<String>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Room;
|
||||
use super::MockRoom;
|
||||
use super::Event;
|
||||
use super::rands;
|
||||
|
||||
fn _dummy() -> MockRoom {
|
||||
let mut r = MockRoom::create(rands());
|
||||
r.since = "1".to_string();
|
||||
let mut events = r.events_r.recv().ok().unwrap();
|
||||
for i in 0..5 {
|
||||
events.push(Event{
|
||||
sender: i.to_string(),
|
||||
since: i.to_string(),
|
||||
body: i.to_string(),
|
||||
});
|
||||
}
|
||||
r.events_s.send(events).ok().unwrap();
|
||||
r
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn randstest() {
|
||||
let a = rands();
|
||||
let b = rands();
|
||||
assert!(a != b, "a == {} == b == {}", a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create() {
|
||||
let mut r: MockRoom = MockRoom::create(rands());
|
||||
println!("{:?}", r.sync());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join() {
|
||||
let rid = "a".to_string();
|
||||
let mut r: MockRoom = MockRoom::join(rands(), rid.to_string());
|
||||
assert!(r.room_id == rid);
|
||||
let events = r.sync();
|
||||
let mut found = false;
|
||||
for e in &events {
|
||||
let j = e.join();
|
||||
if j.is_some() {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
assert!(found);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn since_tracking_push_two() {
|
||||
let mut r = _dummy();
|
||||
r.sync();
|
||||
let mut sinces = vec![];
|
||||
for _ in 0..10 {
|
||||
sinces.push(r.send("0".to_string()).ok().unwrap());
|
||||
sinces.push(r.send("0".to_string()).ok().unwrap());
|
||||
r.sync();
|
||||
assert!(r.since == *sinces.last().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn since_tracking_push_one() {
|
||||
let mut r = _dummy();
|
||||
r.sync();
|
||||
let mut sinces = vec![];
|
||||
for _ in 0..10 {
|
||||
sinces.push(r.send("0".to_string()).ok().unwrap());
|
||||
r.sync();
|
||||
assert!(r.since == *sinces.last().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn since_tracking_push_none() {
|
||||
let mut r = _dummy();
|
||||
r.sync();
|
||||
let mut sinces = vec![];
|
||||
sinces.push(r.send("0".to_string()).ok().unwrap());
|
||||
assert!(r.sync().len() == 1);
|
||||
assert!(r.since == *sinces.last().unwrap(), "after one send: want {:?}, got {:?}: {:?}", *sinces.last().unwrap(), r.since, sinces);
|
||||
assert!(r.sync().len() == 0);
|
||||
assert!(r.since == *sinces.last().unwrap(), "after no send: want {:?}, got {:?}: {:?}", *sinces.last().unwrap(), r.since, sinces);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync() {
|
||||
let mut r = _dummy();
|
||||
let events = r.sync();
|
||||
assert!(events.len() == 3, "want {}, got {}: {:?}", 3, events.len(), events);
|
||||
assert!(events[0].sender == "2");
|
||||
assert!(events[0].body == "2");
|
||||
assert!(events[0].since == "2");
|
||||
assert!(r.since == "4", "want since==4, got {}", r.since);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send() {
|
||||
let mut r = _dummy();
|
||||
let message = "message".to_string();
|
||||
r.sync();
|
||||
assert!(r.send(message.clone()).ok().unwrap().len() > 0);
|
||||
assert!(r.since == "4");
|
||||
let events = r.sync();
|
||||
assert!(events.len() == 1);
|
||||
assert!(events[0].body == message, "want {}, got {}: {:?}", message, events[0].body, events);
|
||||
assert!(r.since != "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rollback() {
|
||||
let mut r = _dummy();
|
||||
let was = r.since.to_string();
|
||||
let events = r.sync();
|
||||
assert!(events.len() == 3);
|
||||
assert!(r.since == "4");
|
||||
r.rollback(was.to_string());
|
||||
assert!(r.since == was);
|
||||
let events = r.sync();
|
||||
assert!(events.len() == 3);
|
||||
assert!(r.since == "4");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
use super::rooms::Rooms;
|
||||
use super::room::Room;
|
||||
use super::mockroom::MockRoom;
|
||||
use super::mockroom::rands;
|
||||
|
||||
// #[derive(Clone, Debug)]
|
||||
pub struct MockRooms {
|
||||
rooms: Vec<MockRoom>,
|
||||
}
|
||||
|
||||
impl MockRooms {
|
||||
pub fn new() -> MockRooms {
|
||||
MockRooms {
|
||||
rooms: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rooms for MockRooms {
|
||||
fn create(&mut self, sender: String) -> Box<dyn Room> {
|
||||
let room = MockRoom::create(sender);
|
||||
let _room = room.room();
|
||||
self.rooms.push(room);
|
||||
Box::new(_room)
|
||||
}
|
||||
|
||||
fn join(&self, sender: String, room_id: String) -> Result<Box<dyn Room>, &str> {
|
||||
for r in &self.rooms {
|
||||
if r.room_id() == room_id {
|
||||
let mut r = r.clone();
|
||||
r.sender = sender;
|
||||
let mut r = r.room();
|
||||
r.send(format!(r#"{{
|
||||
"displayname": "{}",
|
||||
"membership": "join"
|
||||
}}"#, rands())).unwrap();
|
||||
return Ok(Box::new(r));
|
||||
}
|
||||
}
|
||||
Err("not found")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::MockRooms;
|
||||
use super::MockRoom;
|
||||
use super::Rooms;
|
||||
|
||||
fn _dummy() -> MockRooms {
|
||||
let mut mrs = MockRooms::new();
|
||||
for i in 0..5 {
|
||||
let random = MockRoom::create(i.to_string());
|
||||
mrs.rooms.push(random);
|
||||
let joined = MockRoom::join(i.to_string(), i.to_string());
|
||||
mrs.rooms.push(joined);
|
||||
}
|
||||
assert!(mrs.rooms.len() == 10);
|
||||
mrs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mockrooms() {
|
||||
let mrs = MockRooms::new();
|
||||
assert!(mrs.rooms.len() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create() {
|
||||
let mut mrs = _dummy();
|
||||
let was = mrs.rooms.len();
|
||||
let _ = mrs.create("abc".to_string());
|
||||
let is = mrs.rooms.len();
|
||||
assert!(was+1 == is, "was {} rooms, want {} rooms, got {} rooms", was, was+1, is);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_404() {
|
||||
let mrs = _dummy();
|
||||
let was = mrs.rooms.len();
|
||||
let r = mrs.join("?".to_string(), "does not exist".to_string());
|
||||
let is = mrs.rooms.len();
|
||||
assert!(was == is, "was {} rooms, want {} rooms, got {} rooms", was, was+1, is);
|
||||
assert!(!r.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_found() {
|
||||
let mrs = _dummy();
|
||||
let was = mrs.rooms.len();
|
||||
let r = mrs.join("?".to_string(), "0".to_string());
|
||||
let is = mrs.rooms.len();
|
||||
assert!(was == is, "was {} rooms, want {} rooms, got {} rooms", was, was+1, is);
|
||||
assert!(r.is_ok());
|
||||
assert!(r.ok().unwrap().room_id() == "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_clobber() {
|
||||
let mrs = _dummy();
|
||||
let mut a = mrs.join("?".to_string(), "0".to_string()).ok().unwrap();
|
||||
let mut b = mrs.join("?".to_string(), "0".to_string()).ok().unwrap();
|
||||
|
||||
assert!(a.room_id() == b.room_id());
|
||||
|
||||
let ea = a.sync();
|
||||
let eb = b.sync();
|
||||
println!("a1: {:?}, {:?}, {:?}", a.since(), a.room_id(), ea);
|
||||
println!("b1: {:?}, {:?}, {:?}", b.since(), a.room_id(), eb);
|
||||
assert!(ea.len() == eb.len());
|
||||
|
||||
let ea = a.sync();
|
||||
let eb = b.sync();
|
||||
println!("a2: {:?}, {:?}, {:?}", a.since(), a.room_id(), ea);
|
||||
println!("b2: {:?}, {:?}, {:?}", b.since(), a.room_id(), eb);
|
||||
assert!(ea.len() == eb.len());
|
||||
assert!(ea.len() == 0);
|
||||
assert!(eb.len() == 0);
|
||||
|
||||
assert!(a.send("from a".to_string()).is_ok());
|
||||
let ea = a.sync();
|
||||
let eb = b.sync();
|
||||
println!("a3: {:?}, {:?}, {:?}", a.since(), a.room_id(), ea);
|
||||
println!("b3: {:?}, {:?}, {:?}", b.since(), a.room_id(), eb);
|
||||
assert!(ea.len() == 1, "a sent a message and a received {}: {:?}", ea.len(), ea);
|
||||
assert!(eb.len() == 1, "a sent a message and b received {}: {:?}", ea.len(), eb);
|
||||
|
||||
assert!(b.send("from b".to_string()).is_ok());
|
||||
assert!(b.send("from b".to_string()).is_ok());
|
||||
assert!(a.sync().len() == 2);
|
||||
assert!(b.sync().len() == 2);
|
||||
assert!(a.send("from a".to_string()).is_ok());
|
||||
assert!(b.send("from b".to_string()).is_ok());
|
||||
assert!(a.send("from a".to_string()).is_ok());
|
||||
assert!(a.sync().len() == 3);
|
||||
assert!(b.sync().len() == 3);
|
||||
assert!(a.send("from a".to_string()).is_ok());
|
||||
assert!(b.send("from b".to_string()).is_ok());
|
||||
let a = a.sync();
|
||||
let b = b.sync();
|
||||
assert!(a.len() == 2);
|
||||
assert!(b.len() == 2);
|
||||
assert!(a[0].body == "from a");
|
||||
assert!(b[0].body == "from a");
|
||||
assert!(a[1].body == "from b");
|
||||
assert!(b[1].body == "from b");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
pub mod event;
|
||||
pub mod room;
|
||||
pub mod rooms;
|
||||
pub mod mockroom;
|
||||
pub mod mockrooms;
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
use super::event;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
pub trait Room: fmt::Debug {
|
||||
fn rollback(&mut self, since: String);
|
||||
fn sync(&mut self) -> Vec<event::Event>;
|
||||
fn send(&mut self, message: String) -> Result<String, &str>;
|
||||
fn room_id(&self) -> String;
|
||||
fn since(&self) -> String;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Room;
|
||||
use super::super::mockroom::MockRoom;
|
||||
|
||||
#[test]
|
||||
fn mockroom() {
|
||||
fn gen() -> impl Room {
|
||||
let r = MockRoom::create("123".to_string());
|
||||
r
|
||||
}
|
||||
gen();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
use super::room::Room;
|
||||
|
||||
pub trait Rooms {
|
||||
fn create(&mut self, sender: String) -> Box<dyn Room>;
|
||||
fn join(&self, sender: String, room_id: String) -> Result<Box<dyn Room>, &str>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Rooms;
|
||||
use super::Room;
|
||||
use super::super::mockrooms::MockRooms;
|
||||
|
||||
#[test]
|
||||
fn mockrooms() {
|
||||
fn gen() -> impl Rooms {
|
||||
let r = MockRooms::new();
|
||||
r
|
||||
}
|
||||
let mut rooms = gen();
|
||||
let mut room_ptr: Box<dyn Room> = rooms.create("abc".to_string());
|
||||
assert!(room_ptr.send("hi".to_string()).is_ok());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue