bel 2021-09-08 14:17:24 -06:00
commit 98baeb154e
25 changed files with 2583 additions and 0 deletions

272
secert-hitler/Cargo.lock generated Normal file
View File

@ -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"

14
secert-hitler/Cargo.toml Normal file
View File

@ -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 = "*"

View File

@ -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
}

View File

@ -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());
}
}

View File

@ -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 &params {
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 &params {
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);
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,4 @@
pub mod gamemaster;
mod lobby;
mod setup;
mod game;

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1 @@
pub mod gamemaster;

23
secert-hitler/src/main.rs Normal file
View File

@ -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()
*/
}

View File

@ -0,0 +1 @@
pub mod state;

View File

@ -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());
}
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -0,0 +1,5 @@
pub mod event;
pub mod room;
pub mod rooms;
pub mod mockroom;
pub mod mockrooms;

View File

@ -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();
}
}

View File

@ -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());
}
}

View File