new main menu and game list
authorGeoffrey Allott <geoffrey@allott.email>
Sat, 13 Mar 2021 20:17:48 +0000 (20:17 +0000)
committerGeoffrey Allott <geoffrey@allott.email>
Sat, 13 Mar 2021 20:17:48 +0000 (20:17 +0000)
16 files changed:
Cargo.lock
Cargo.toml
site/img/holdem.svg [new file with mode: 0644]
site/img/whist.svg [new file with mode: 0644]
site/modules/gamelist.js
site/modules/mainmenu.js [new file with mode: 0644]
site/modules/socket.js
site/modules/words.js [new file with mode: 0644]
site/style.css
site/style/game-list.css
site/style/mainmenu.css [new file with mode: 0644]
site/style/poker.css
src/api.rs
src/client.rs
src/filter.rs [new file with mode: 0644]
src/main.rs

index 64a8404333e8c15d4c857267b580d0d1f15baa10..50c7c4d47c0eaca731bba817e94dfdc2f279ea8e 100644 (file)
@@ -78,6 +78,12 @@ version = "1.0.38"
 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"
@@ -356,6 +362,18 @@ version = "1.2.1"
 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"
@@ -698,6 +716,12 @@ dependencies = [
  "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"
@@ -1030,6 +1054,19 @@ version = "1.4.0"
 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"
@@ -1092,6 +1129,19 @@ dependencies = [
  "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"
@@ -1215,6 +1265,7 @@ dependencies = [
  "hex",
  "itertools",
  "log",
+ "nom",
  "pin-project",
  "rand 0.8.3",
  "rand_chacha 0.3.0",
@@ -1290,6 +1341,12 @@ dependencies = [
  "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"
@@ -1702,6 +1759,12 @@ dependencies = [
  "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"
@@ -1780,6 +1843,12 @@ dependencies = [
  "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"
@@ -2258,3 +2327,9 @@ name = "winapi-x86_64-pc-windows-gnu"
 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"
index 37eda431f99fccd2cef18b486926dd71dbbf5ff1..d84cea7f97953ea16b87d0ae8a7041cf8159f2ec 100644 (file)
@@ -13,6 +13,7 @@ futures = "0.3"
 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"
diff --git a/site/img/holdem.svg b/site/img/holdem.svg
new file mode 100644 (file)
index 0000000..e5ab9af
--- /dev/null
@@ -0,0 +1,8 @@
+<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>
diff --git a/site/img/whist.svg b/site/img/whist.svg
new file mode 100644 (file)
index 0000000..8f8178d
--- /dev/null
@@ -0,0 +1,8 @@
+<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>
index 52071906f219157dff93d074af9ef78f28847bab..193e43c7bce2baab94d823b08ef8e8066fbfa431 100644 (file)
@@ -1,30 +1,30 @@
+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) {
@@ -53,37 +53,175 @@ export class GameList {
     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);
     }
 }
diff --git a/site/modules/mainmenu.js b/site/modules/mainmenu.js
new file mode 100644 (file)
index 0000000..b561872
--- /dev/null
@@ -0,0 +1,35 @@
+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);
+    }
+}
index f4edb7dd28ac2c6aac75cbc818b78213e22fa2c7..987bee811a4e81faf440d2660162a907d7962053 100644 (file)
@@ -1,5 +1,6 @@
 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";
@@ -53,7 +54,7 @@ export class Socket {
                 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":
@@ -61,7 +62,7 @@ export class Socket {
                 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":
diff --git a/site/modules/words.js b/site/modules/words.js
new file mode 100644 (file)
index 0000000..080e01b
--- /dev/null
@@ -0,0 +1,65 @@
+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)];
+}
index b2559271a11fa82718aeb6aea0d8b36d7353ade4..e3f71c0a179fbf8d81d3a37ffb22445cd23cdc8f 100644 (file)
@@ -1,4 +1,5 @@
 @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");
index d2506f524eb3de4664d581bb4ab773c110e3d19b..03d76b86fd7b42c2c0ec07f4520b4f8a80c12090 100644 (file)
@@ -3,7 +3,7 @@
     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;
 }
diff --git a/site/style/mainmenu.css b/site/style/mainmenu.css
new file mode 100644 (file)
index 0000000..0965336
--- /dev/null
@@ -0,0 +1,33 @@
+.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;
+}
index ff6cb75e457201f5307d33b453e816ac01ce4dc2..00ce239a4941b88108838db1bea3ac67645c4869 100644 (file)
@@ -10,6 +10,7 @@
     stroke-width: 1px;
     transition: fill 0.5s;
     pointer-events: none;
+    cursor: pointer;
 }
 
 .fold-control.active, .call-control.active, .bet-control.active {
index 1f9e581622cf95a0a951740ba6c255652c3219a0..2817214409d1f569e1bb9bc9f53de7916ea76640 100644 (file)
@@ -44,7 +44,7 @@ pub enum ServerMessage {
     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> },
index bbe6cdf1f0233fcf3aa2b40863ce879d478d4c13..0169278704110ae612cc0131bcabd6b6d501247c 100644 (file)
@@ -132,14 +132,14 @@ impl ConnectionState {
                 }
             }
             (&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() },
                 }
diff --git a/src/filter.rs b/src/filter.rs
new file mode 100644 (file)
index 0000000..ba6c303
--- /dev/null
@@ -0,0 +1,92 @@
+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);
+    }
+}
index c4f4e963c3d4bab0765a719a430a2f030234e738..c921544a26fc5db7116918615c9a295958b9585d 100644 (file)
@@ -28,6 +28,7 @@ mod card;
 mod client;
 mod config;
 mod dealer;
+mod filter;
 mod game;
 mod pubsub;
 mod rng;