source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1"
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
[[package]]
name = "async-attributes"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+[[package]]
+name = "bitvec"
+version = "0.19.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
[[package]]
name = "block-buffer"
version = "0.9.0"
"percent-encoding",
]
+[[package]]
+name = "funty"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
+
[[package]]
name = "futures"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+[[package]]
+name = "lexical-core"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374"
+dependencies = [
+ "arrayvec",
+ "bitflags",
+ "cfg-if 1.0.0",
+ "ryu",
+ "static_assertions",
+]
+
[[package]]
name = "libc"
version = "0.2.88"
"socket2",
]
+[[package]]
+name = "nom"
+version = "6.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
+dependencies = [
+ "bitvec",
+ "funty",
+ "lexical-core",
+ "memchr",
+ "version_check",
+]
+
[[package]]
name = "num_cpus"
version = "1.13.0"
"hex",
"itertools",
"log",
+ "nom",
"pin-project",
"rand 0.8.3",
"rand_chacha 0.3.0",
"proc-macro2",
]
+[[package]]
+name = "radium"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
+
[[package]]
name = "rand"
version = "0.7.3"
"version_check",
]
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "stdweb"
version = "0.4.20"
"unicode-xid",
]
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
[[package]]
name = "tempfile"
version = "3.2.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "wyz"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
getrandom = "0.2"
hex = { version = "0.4", features = ["serde"] }
itertools = "0.10"
+nom = "6"
pin-project = "1.0"
rand = "0.8"
rand_chacha = "0.3"
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" width="225" height="350">
+ <rect x="5" y="5" width="215" height="340" fill="#fffff8" stroke="black" stroke-width="5" rx="25" />
+ <g id="text">
+ <text x="165" y="60" style="font: bold 50px serif;">A</text>
+ <text x="150" y="120" style="font: bold 75px serif;">♠</text>
+ </g>
+ <use href="#text" transform="rotate(180 112.5 175)" />
+</svg>
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" width="225" height="350">
+ <rect x="5" y="5" width="215" height="340" fill="#fffff8" stroke="black" stroke-width="5" rx="25" />
+ <g id="text">
+ <text x="165" y="60" style="font: bold 50px serif;">A</text>
+ <text x="150" y="120" style="font: bold 75px serif;">♣</text>
+ </g>
+ <use href="#text" transform="rotate(180 112.5 175)" />
+</svg>
+import { random_title } from "./words.js";
+
export class GameList {
- constructor(container, games, send) {
+ constructor(container, filter, games, send) {
this.container = container;
this.games = games;
this.send = send;
+ this.container.innerText = "";
+
const game_list_outside = document.createElement("div");
game_list_outside.classList.add("game-list-outside");
this.game_list_container = document.createElement("div");
this.game_list_container.classList.add("game-list-container");
- this.game_list = document.createElement("div");
+ this.game_list = document.createElement("table");
this.game_list.classList.add("game-list");
this.game_list_container.append(this.game_list);
game_list_outside.append(this.game_list_container);
+ const game_list_new = document.createElement("button");
+ game_list_new.classList.add("game-list-new");
+ game_list_new.innerText = "New";
+ game_list_new.onclick = () => this.show_new_game_popup();
+ game_list_outside.append(game_list_new);
this.game_list_filter_input = document.createElement("input");
+ this.game_list_filter_input.value = filter;
game_list_outside.append(this.game_list_filter_input);
- const game_list_options = document.createElement("button");
- const game_list_option_menu = document.createElement("div");
- game_list_option_menu.classList.add("game-list-option-menu");
- game_list_option_menu.classList.add("hidden");
- const game_list_option_logout = document.createElement("div");
- game_list_option_logout.innerText = "Logout";
- game_list_option_logout.onclick = () => this.send({type: "Logout"});
- game_list_option_menu.append(game_list_option_logout);
- game_list_options.append(game_list_option_menu);
- game_list_options.onclick = () => game_list_option_menu.classList.toggle("hidden");
- game_list_outside.append(game_list_options);
+
this.container.append(game_list_outside);
for (const game of this.games) {
game_element(game) {
console.log("getting game element for ", game);
- const game_element = document.createElement("div");
+ const game_element = document.createElement("tr");
game_element.classList.add("game-summary");
- const id = document.createElement("div");
+ const id = document.createElement("td");
id.innerText = "#" + game.id;
id.classList.add("game-id");
game_element.append(id);
- const title = document.createElement("div");
+ const format = document.createElement("td");
+ format.innerText = game.settings.format;
+ format.classList.add("game-format");
+ game_element.append(format);
+
+ const title = document.createElement("td");
title.innerText = game.settings.title;
title.classList.add("game-title");
game_element.append(title);
- const format = document.createElement("div");
- format.innerText = game.settings.format;
- format.classList.add("game-format");
- game_element.append(format);
+ game_element.onclick = () => this.show_game_popup(game);
+
+ return game_element;
+ }
+
+ clear_game_popup(game) {
+ this.container.querySelectorAll(".game-popup-container").forEach(element => element.remove());
+ }
+
+ show_game_popup(game) {
+ this.clear_game_popup();
+
+ const game_popup_container = document.createElement("div");
+ game_popup_container.classList.add("game-popup-container");
+ game_popup_container.onclick = () => this.clear_game_popup();
+ const game_popup = document.createElement("div");
+ game_popup.classList.add("game-popup");
+ game_popup.onclick = event => event.stopPropagation();
+
+ const table = document.createElement("table");
+
+ const title_row = document.createElement("tr");
+ const title_value = document.createElement("th");
+ title_value.setAttribute("colspan", 2);
+ title_value.innerText = game.settings.title;
+ title_value.classList.add("game-title");
+ title_row.append(title_value);
+ table.append(title_row);
+
+ const id_row = document.createElement("tr");
+ const id_header = document.createElement("th");
+ id_header.innerText = "id";
+ const id_value = document.createElement("td");
+ id_value.innerText = game.id;
+ id_row.append(id_header);
+ id_row.append(id_value);
+ table.append(id_row);
- const settings = document.createElement("ul");
- settings.classList.add("game-settings");
for (const setting of Object.keys(game.settings)) {
- if (setting !== "id" && setting !== "title" && setting !== "format") {
- const li = document.createElement("li");
- li.innerText = setting + ": " + game.settings[setting];
- settings.append(li);
+ if (setting === "title") continue;
+ const row = document.createElement("tr");
+ const header = document.createElement("th");
+ header.innerText = setting;
+ const value = document.createElement("td");
+ value.innerText = game.settings[setting];
+ row.append(header);
+ row.append(value);
+ table.append(row);
+ }
+
+ game_popup.append(table);
+
+ const button = document.createElement("button");
+ button.innerText = "Join";
+ button.onclick = () => this.send({type: "JoinGame", id: game.id});
+ game_popup.append(button);
+
+ game_popup_container.append(game_popup);
+
+ this.container.append(game_popup_container);
+ }
+
+ show_new_game_popup() {
+ this.clear_game_popup();
+
+ const game_popup_container = document.createElement("div");
+ game_popup_container.classList.add("game-popup-container");
+ game_popup_container.onclick = () => this.clear_game_popup();
+ const game_popup = document.createElement("div");
+ game_popup.classList.add("game-popup");
+ game_popup.onclick = event => event.stopPropagation();
+
+ const settings = {};
+
+ const table = document.createElement("table");
+
+ const title_row = document.createElement("tr");
+ const title_value = document.createElement("th");
+ title_value.setAttribute("colspan", 2);
+ const title_input = document.createElement("input");
+ title_input.classList.add("game-title");
+ title_input.value = random_title();
+ title_input.onchange = () => settings["title"] = title_input.value;
+ title_input.onchange();
+ title_value.append(title_input);
+ title_row.append(title_value);
+ table.append(title_row);
+
+ const format_row = document.createElement("tr");
+ const format_title = document.createElement("th");
+ format_title.innerText = "Format";
+ format_row.append(format_title);
+ const format_value = document.createElement("td");
+ const format_input = document.createElement("select");
+ for (const format of ["KnockOutWhist", "TexasHoldEm"]) {
+ const game_format = document.createElement("option");
+ game_format.setAttribute("value", format);
+ game_format.innerText = format;
+ format_input.append(game_format);
+ if (this.game_list_filter_input.value.includes(format)) {
+ format_input.value = format;
}
}
- game_element.append(settings);
+ format_input.onchange = () => {
+ settings["format"] = format_input.value;
+ table.className = format_input.value;
+ };
+ format_input.onchange();
+ format_value.append(format_input);
+ format_row.append(format_value);
+ table.append(format_row);
- game_element.onclick = () => this.send({type: "JoinGame", id: game.id});
+ const fields = [
+ { name: "max_players", display: "Max Players", value: 2, formats: ["TexasHoldEm", "KnockOutWhist"] },
+ { name: "small_blind", display: "Small Blind", value: 25, formats: ["TexasHoldEm"] },
+ { name: "starting_stack", display: "Starting Stack", value: 1000, formats: ["TexasHoldEm"] },
+ { name: "action_timeout", display: "Action Timeout", value: 30000, formats: ["TexasHoldEm"] },
+ ];
- return game_element;
+ for (const {name, display, value, formats} of fields) {
+ const row = document.createElement("tr");
+ row.classList.add("game-field");
+ for (const format of formats) {
+ row.classList.add(format);
+ }
+ const title = document.createElement("th");
+ title.innerText = display;
+ row.append(title);
+ const cell = document.createElement("td");
+ const input = document.createElement("input");
+ input.setAttribute("type", "number");
+ input.value = value;
+ input.onchange = () => settings[name] = Number(input.value);
+ input.onchange();
+ cell.append(input);
+ row.append(cell);
+ table.append(row);
+ }
+
+ game_popup.append(table);
+
+ const button = document.createElement("button");
+ button.innerText = "Create";
+ button.onclick = () => {
+ this.send({type: "CreateGame", settings});
+ this.clear_game_popup();
+ };
+ game_popup.append(button);
+
+ game_popup_container.append(game_popup);
+
+ this.container.append(game_popup_container);
}
}
--- /dev/null
+export class MainMenu {
+ constructor(container, send) {
+ this.container = container;
+ this.send = send;
+
+ this.container.innerText = "";
+
+ const menu_container = document.createElement("div");
+ menu_container.classList.add("main-menu");
+
+ const texas_hold_em = document.createElement("div");
+ texas_hold_em.classList.add("game-format-icon");
+ const texas_hold_em_img = document.createElement("img");
+ texas_hold_em_img.setAttribute("src", "img/holdem.svg");
+ const texas_hold_em_text = document.createElement("p");
+ texas_hold_em_text.innerText = "Texas Hold 'em";
+ texas_hold_em.append(texas_hold_em_img);
+ texas_hold_em.append(texas_hold_em_text);
+ texas_hold_em.onclick = () => this.send({type: "JoinLobby", filter: "format: TexasHoldEm"});
+ menu_container.append(texas_hold_em);
+
+ const knock_out_whist = document.createElement("div");
+ knock_out_whist.classList.add("game-format-icon");
+ const knock_out_whist_img = document.createElement("img");
+ knock_out_whist_img.setAttribute("src", "img/whist.svg");
+ const knock_out_whist_text = document.createElement("p");
+ knock_out_whist_text.innerText = "Knock-Out Whist";
+ knock_out_whist.append(knock_out_whist_img);
+ knock_out_whist.append(knock_out_whist_text);
+ knock_out_whist.onclick = () => this.send({type: "JoinLobby", filter: "format: KnockOutWhist"});
+ menu_container.append(knock_out_whist);
+
+ this.container.append(menu_container);
+ }
+}
const svgns = "http://www.w3.org/2000/svg";
+import { MainMenu } from "./mainmenu.js";
import { GameList } from "./gamelist.js";
import { Chatroom } from "./chatroom.js";
import { KnockOutWhist } from "./whist.js";
break;
case "LoginSuccess":
this.hide_login();
- this.send({type: "JoinLobby", filter: ""});
+ this.game = new MainMenu(this.container, message => this.send(message));
this.state = "LoggedIn";
break;
case "JoinLobbyFailure":
this.state = "LoggedIn";
break;
case "JoinLobbySuccess":
- this.game = new GameList(this.container, message.games, message => this.send(message))
+ this.game = new GameList(this.container, message.filter, message.games, message => this.send(message))
this.state = "InLobby";
break;
case "NewGame":
--- /dev/null
+const adjectives = [
+ "Amazing",
+ "Based",
+ "Crazy",
+ "Devilish",
+ "Exciting",
+ "Fabulous",
+ "Great",
+ "Historic",
+ "Incredible",
+ "Jacked",
+ "Kingly",
+ "Luxurious",
+ "Mega",
+ "Nice",
+ "Optimal",
+ "Peerless",
+ "Quality",
+ "Renowned",
+ "Superb",
+ "Terrific",
+ "Ultimate",
+ "Vivacious",
+ "Wholesome",
+ "Xenial",
+ "Youthful",
+ "Zesty",
+];
+
+const nouns = [
+ "Adventure",
+ "Business",
+ "Contest",
+ "Diversion",
+ "Event",
+ "Festivity",
+ "Game",
+ "Huddle",
+ "Incident",
+ "Juncture",
+ "Kingdom",
+ "Lark",
+ "Merriment",
+ "Nucleus",
+ "Occupation",
+ "Pastime",
+ "Quorum",
+ "Recreation",
+ "Story",
+ "Tournament",
+ "Undertaking",
+ "Venture",
+ "Wonder",
+ "Xenolith",
+ "Yarn",
+ "Zeitgeist",
+];
+
+function random_int(max) {
+ return Math.floor(Math.random() * max);
+}
+
+export function random_title() {
+ return adjectives[random_int(26)] + " " + nouns[random_int(26)];
+}
@import url("style/login.css");
+@import url("style/mainmenu.css");
@import url("style/game-list.css");
@import url("style/chatroom.css");
@import url("style/poker.css");
height: 100%;
display: grid;
overflow: hidden;
- font-size: 4vw;
+ font-size: 3vw;
grid: auto-flow / 1fr 8vw;
}
font-size: inherit;
}
-.game-list-option-menu {
- display: block;
- position: absolute;
- bottom: 6vw;
- right: 0;
-}
-
-.game-list-option-menu > div {
- padding-left: 2vw;
- padding-right: 2vw;
- border: 1px solid grey;
- font-size: 8vw;
- background-color: white;
-}
-
-.game-list-option-menu > div:hover {
- background-color: #dfefff;
-}
-
.game-list-container {
grid-column: 1 / span 2;
grid-row: 1;
.game-list {
width: 100%;
+ border-collapse: collapse;
+}
+
+.game-popup-container {
+ background-color: rgba(0, 0, 100, 0.5);
+ width: 100%;
+ height: 100%;
+ position: fixed;
+ display: grid;
+ grid: 1fr 2fr 1fr / 1fr 2fr 1fr;
+}
+
+.game-popup {
+ grid-column: 2 / 3;
+ grid-row: 2 / 3;
+ background-color: skyblue;
+ border-radius: 2vw;
+ padding: 4vw;
+ font-size: 3vw;
+}
+
+.game-popup > table {
+ border-collapse: collapse;
+ width: 100%;
+ height: calc(100% - 4vw);
+}
+
+.game-popup > table.TexasHoldEm tr.game-field {
+ display: none;
+}
+
+.game-popup > table.TexasHoldEm tr.game-field.TexasHoldEm {
+ display: table-row;
+}
+
+.game-popup > table.KnockOutWhist tr.game-field {
+ display: none;
+}
+
+.game-popup > table.KnockOutWhist tr.game-field.KnockOutWhist {
+ display: table-row;
+}
+
+.game-popup input, .game-popup select {
+ width: 100%;
+ height: 100%;
+ font-size: inherit;
+ text-align: right;
+}
+
+.game-popup .game-title {
+ text-align: center;
+}
+
+.game-popup th {
+ background-color: darkgrey;
+ border: 1px solid black;
+}
+
+.game-popup td {
+ background-color: lightgrey;
+ border: 1px solid black;
+ text-align: right;
+}
+
+.game-popup > button {
+ width: 100%;
+ font-size: 4vw;
}
@keyframes new-game {
.game-summary {
border: 1px solid grey;
border-top: none;
- height: 30vw;
font-family: sans;
- display: grid;
- grid: 'id title format'
- 'settings settings settings'
- 'settings settings settings';
- /*animation: new-game 2s; TODO */
cursor: pointer;
}
}
.game-id {
- width: 25vw;
font-style: italic;
color: grey;
- grid-area: id;
}
.game-title {
- text-align: center;
font-weight: bold;
- grid-area: title;
- justify-self: center;
-}
-
-.game-format {
- width: 25vw;
- grid-area: format;
- justify-self: end;
- text-align: right;
-}
-
-.game-settings {
- grid-area: settings;
}
--- /dev/null
+.main-menu {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+}
+
+.game-format-icon {
+ width: 200px;
+ height: 200px;
+ margin: 20px;
+ padding: 10px;
+ background: #8080ff;
+ border: 2px solid black;
+ border-radius: 20px;
+ cursor: pointer;
+}
+
+.game-format-icon:hover {
+ background: #a0a0ff;
+}
+
+.game-format-icon > img {
+ width: 200px;
+ height: 160px;
+ cursor-events: none;
+}
+
+.game-format-icon > p {
+ text-align: center;
+ font-family: sans;
+ font-size: 20px;
+ cursor-events: none;
+}
stroke-width: 1px;
transition: fill 0.5s;
pointer-events: none;
+ cursor: pointer;
}
.fold-control.active, .call-control.active, .bet-control.active {
LogoutSuccess,
CreateGameSuccess { id: i64 },
CreateGameFailure { reason: String },
- JoinLobbySuccess { games: Vec<GameSummary> },
+ JoinLobbySuccess { filter: String, games: Vec<GameSummary> },
JoinLobbyFailure { reason: String },
NewGame { game: GameSummary },
JoinGameSuccess { summary: GameSummary, actions: Vec<UserAction> },
}
}
(&mut ClientState::LoggedIn { username, .. }, ClientMessage::JoinLobby { filter }) => {
- let mut game_list = GameList::new(filter);
+ let mut game_list = GameList::new(filter.clone());
match self.server.game_list(0).await {
Ok(games) => {
for game in games.clone() {
game_list.push(game);
}
self.client = ClientState::LoggedIn { username, state: LoggedInState::InLobby { game_list } };
- ServerMessage::JoinLobbySuccess { games }
+ ServerMessage::JoinLobbySuccess { filter, games }
}
Err(err) => ServerMessage::JoinLobbyFailure { reason: err.to_string() },
}
--- /dev/null
+use nom::{error::ParseError, IResult, sequence::{delimited, preceded, terminated, tuple}, multi::{many0_count, many1, separated_list1}, bytes::complete::{escaped_transform, tag, take_while1, take_until}, character::{is_alphabetic, complete::{alpha1, multispace0, none_of}}, combinator::{map, value}, branch::alt};
+
+fn ws<'a, F: 'a, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
+where
+ F: FnMut(&'a str) -> IResult<&'a str, O, E>,
+{
+ delimited(
+ multispace0,
+ inner,
+ multispace0
+ )
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Field<'a> {
+ Format(&'a str),
+ Title(&'a str),
+ Parens(Box<Filter<'a>>),
+}
+
+fn parse_field(input: &str) -> IResult<&str, Field> {
+ let string = || alt((alpha1, delimited(tag("\""), take_until("\""), tag("\""))));
+ let format = map(preceded(tuple((tag("format"), ws(tag(":")))), string()), Field::Format);
+ let title = map(preceded(tuple((tag("title"), ws(tag(":")))), string()), Field::Title);
+ let parens = delimited(tag("("), map(map(ws(parse_filter), Box::new), Field::Parens), tag(")"));
+ alt((format, title, parens))(input)
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Not<'a> {
+ negate: bool,
+ field: Field<'a>,
+}
+
+fn parse_not(input: &str) -> IResult<&str, Not> {
+ map(tuple((many0_count(ws(tag("not"))), parse_field)), |(count, field)| Not { negate: count % 2 == 1, field })(input)
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct And<'a>(Vec<Not<'a>>);
+
+fn parse_and(input: &str) -> IResult<&str, And> {
+ map(separated_list1(ws(tag("and")), parse_not), And)(input)
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Or<'a>(Vec<And<'a>>);
+
+fn parse_or(input: &str) -> IResult<&str, Or> {
+ map(separated_list1(ws(tag("or")), parse_and), Or)(input)
+}
+
+type Filter<'a> = Or<'a>;
+
+fn parse_filter(input: &str) -> IResult<&str, Filter> {
+ parse_or(input)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn field_parse() {
+ assert_eq!(Field::Format("KnockOutWhist"), parse_field("format: KnockOutWhist").unwrap().1);
+ assert_eq!(Field::Title("Superb Game"), parse_field("title: \"Superb Game\"").unwrap().1);
+ }
+
+ #[test]
+ fn filter_parse() {
+ let expected = Or(vec![
+ And(vec![
+ Not {
+ negate: false,
+ field: Field::Parens(Box::new(
+ Or(vec![
+ And(vec![
+ Not { negate: false, field: Field::Format("KnockOutWhist") },
+ Not { negate: true, field: Field::Title("Superb Game") },
+ ])
+ ])
+ ))
+ }
+ ]),
+ And(vec![
+ Not { negate: false, field: Field::Format("TexasHoldEm") },
+ ])
+ ]);
+ let actual = parse_filter("(format: KnockOutWhist and not title: \"Superb Game\") or format: TexasHoldEm").unwrap().1;
+ assert_eq!(expected, actual);
+ }
+}
mod client;
mod config;
mod dealer;
+mod filter;
mod game;
mod pubsub;
mod rng;