add cribbage game to site
authorGeoffrey Allott <geoffrey@allott.email>
Sun, 4 Jun 2023 22:28:30 +0000 (23:28 +0100)
committerGeoffrey Allott <geoffrey@allott.email>
Sun, 4 Jun 2023 22:28:30 +0000 (23:28 +0100)
15 files changed:
site/img/cribbage.svg [new file with mode: 0644]
site/modules/chatroom.js
site/modules/cribbage.js [new file with mode: 0644]
site/modules/game.js [new file with mode: 0644]
site/modules/gamechat.js [new file with mode: 0644]
site/modules/gamelist.js
site/modules/mainmenu.js
site/modules/poker.js
site/modules/socket.js
site/modules/svg.js [new file with mode: 0644]
site/modules/whist.js
site/modules/winner.js
site/style.css
site/style/cribbage.css [new file with mode: 0644]
site/style/game-list.css

diff --git a/site/img/cribbage.svg b/site/img/cribbage.svg
new file mode 100644 (file)
index 0000000..8ee50ef
--- /dev/null
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="180" height="180">
+    <!-- TODO -->
+</svg>
index ddd669ae4fca876da550d00f1f47afaa0aee3add..7911d01b1f0227162fd8c63d618376f8702ccd2e 100644 (file)
@@ -112,6 +112,12 @@ export class Chatroom {
             case "PlayCard":
                 this.chat.append(this.info_element(user_action.username, "Plays " + card_name(user_action.action.card)));
                 break;
+            case "Pass":
+                this.chat.append(this.info_element(user_action.username, "Passes"));
+                break;
+            case "Score":
+                this.chat.append(this.info_element(user_action.username, "Scores " + user_action.action.points + ": " + user_action.action.reason));
+                break;
             case "CutCard":
                 this.chat.append(this.info_element(user_action.username, "Cuts " + card_name(user_action.action.card)));
                 break;
@@ -124,6 +130,13 @@ export class Chatroom {
             case "WinCall":
                 this.chat.append(this.info_element(user_action.username, "Wins the call"));
                 break;
+            case "PutInBox":
+                if (user_action.action.card === null) {
+                    this.chat.append(this.info_element(user_action.username, "Puts a card in the box"));
+                } else {
+                    this.chat.append(this.info_element(user_action.username, "Puts " + card_name(user_action.action.card) + " in the box"));
+                }
+                break;
             case "WinHand":
                 this.chat.append(this.info_element(
                     user_action.username,
diff --git a/site/modules/cribbage.js b/site/modules/cribbage.js
new file mode 100644 (file)
index 0000000..2c4aeba
--- /dev/null
@@ -0,0 +1,285 @@
+import { create_svg_element } from "./svg.js";
+import { card_href } from "./card.js";
+import { GameWithChat } from "./gamechat.js";
+import { CongratulateWinner } from "./winner.js";
+
+export class Cribbage extends GameWithChat {
+    constructor(container, summary, actions, username, send, close) {
+        super(container, summary, actions, username, send, close);
+        this.svg.classList.add("cribbage");
+
+        this.user_icons = new Map();
+        this.hands = new Map();
+        this.played = new Map();
+        this.scores = new Map();
+        this.community = [];
+        this.box = [];
+
+        this.active = null;
+        this.dealer = null;
+
+        this.pass_control = create_svg_element(this.svg, "rect", ["pass-control", "active"], [["x", "400"], ["y", "400"], ["width", "120"], ["height", "50"], ["rx", "10"]]);
+        this.pass_control.onclick = () => {
+            this.send({type: "TakeAction", action: {action: "Pass"}});
+        };
+        this.pass_control_label = create_svg_element(this.svg, "text", ["pass-control-label"], [["x", "460"], ["y", "430"]]);
+        this.pass_control_text = document.createTextNode("Pass");
+        this.pass_control_label.append(this.pass_control_text);
+
+        this.take_initial_actions(actions);
+    }
+
+    redraw_players() {
+        const active_player = this.active;
+        for (const [username, [icon, score, active]] of this.user_icons) {
+            if (!this.seats.has(username)) {
+                this.svg.removeChild(icon);
+                this.svg.removeChild(score);
+                this.svg.removeChild(active);
+                this.user_icons.delete(username);
+            } else {
+                const seat = this.seats.get(username);
+                const angle = this.player_angle(seat);
+                const x = 240 - 180 * Math.sin(angle);
+                const y = 250 + 120 * Math.cos(angle) + 50 * Math.sign(Math.cos(angle));
+                icon.setAttribute("x", x);
+                icon.setAttribute("y", y);
+                score.childNodes[0].nodeValue = this.scores.get(username);
+                score.setAttribute("x", 240 - 120 * Math.sin(angle));
+                score.setAttribute("y", 250 + 70 * Math.cos(angle));
+                active.classList.toggle("active", active_player === username);
+                active.setAttribute("cx", x - 10);
+                active.setAttribute("cy", y - 5);
+            }
+        }
+        for (const [username, seat] of this.seats) {
+            if (!this.user_icons.has(username)) {
+                const angle = this.player_angle(seat);
+                const x = 240 - 180 * Math.sin(angle);
+                const y = 250 + 120 * Math.cos(angle) + 50 * (Math.sign(Math.cos(angle)) || 1);
+
+                const icon = create_svg_element(this.svg, "text", [], [["x", x], ["y", y]]);
+                icon.append(document.createTextNode(username));
+
+                const score = create_svg_element(this.svg, "text", [], [["x", 240 - 120 * Math.sin(angle)], ["y", 250 + 70 * Math.cos(angle)]]);
+                score.append(document.createTextNode(this.scores.get(username) || ""));
+
+                const active = create_svg_element(this.svg, "circle", ["active-indicator"], [["cx", x - 10], ["cy", y - 5], ["r", 5]]);
+                active.classList.toggle("active", active_player === username);
+
+                this.user_icons.set(username, [icon, score, active]);
+            }
+        }
+    }
+
+    card_image(username, card) {
+        const image = create_svg_element(this.svg, card === null ? "image" : "use", [], [["width", "45"], ["height", "70"], ["href", card_href(card)]]);
+        if (username === this.username) {
+            image.onclick = () => this.send({
+                type: "TakeAction",
+                action: {
+                    action: this.community.length === 0 ? "PutInBox" : "PlayCard",
+                    card: card
+                }
+            });
+        }
+        return image;
+    }
+
+    redraw_cards() {
+        for (const [username, cards] of this.hands) {
+            const seat = this.seats.get(username);
+            const angle = this.player_angle(seat);
+            const offset = cards.length * 10;
+            let x = 227.5 + offset - 180 * Math.sin(angle);
+            const y = 210 + 120 * Math.cos(angle);
+            for (const {card, image} of cards) {
+                image.classList.toggle("my-card", username === this.username);
+                image.setAttribute("x", x);
+                image.setAttribute("y", y);
+                x -= 20;
+            }
+        }
+        for (const [username, cards] of this.played) {
+            const seat = this.seats.get(username);
+            const angle = this.player_angle(seat);
+            const offset = cards.length * 10;
+            const x = 227.5 + offset - 90 * Math.sin(angle);
+            const y = 210 + 30 * Math.cos(angle);
+            let adjust = 0;
+            for (const {card, image} of cards) {
+                image.classList.toggle("my-card", username === this.username);
+                image.setAttribute("x", x + 10 * Math.sin(adjust));
+                image.setAttribute("y", y + 10 * Math.cos(adjust));
+                adjust += 77;
+            }
+        }
+        let x = 120.0;
+        for (const {card, image} of this.community) {
+            const y = 210;
+            image.setAttribute("x", x);
+            image.setAttribute("y", y);
+            x -= 20;
+        }
+        x = 400.0;
+        for (const {card, image} of this.box) {
+            const y = 210;
+            image.setAttribute("x", x);
+            image.setAttribute("y", y);
+            x -= 20;
+        }
+    }
+
+    remove_card(username, card) {
+        const hand = this.hands.get(username);
+        const card_index = hand.findIndex(c => c.card !== null && c.card.suit === card.suit && c.card.rank === card.rank);
+        const null_index = hand.findIndex(c => c.card === null);
+        if (card_index !== -1) {
+            return hand.splice(card_index, 1)[0];
+        } else if (null_index !== -1) {
+            return hand.splice(null_index, 1)[0];
+        } else {
+            return undefined;
+        }
+    }
+
+    reveal_card(username, card) {
+        const hand = this.hands.get(username);
+        const found_card = hand.find(c => c.card !== null && c.card.suit === card.suit && c.card.rank === card.rank) || hand.find(c => c.card === null);
+        const found_card_box = this.box.find(c => c.card !== null && c.card.suit === card.suit && c.card.rank === card.rank) || this.box.find(c => c.card === null);
+        if (found_card !== undefined) {
+            this.svg.removeChild(found_card.image);
+            found_card.card = card;
+            found_card.image = this.card_image(username, card);
+        } else if (found_card_box !== undefined) {
+            this.svg.removeChild(found_card_box.image);
+            found_card_box.card = card;
+            found_card_box.image = this.card_image(null, card);
+        }
+    }
+
+    take_action(user_action) {
+        super.take_action(user_action);
+        switch (user_action.action.action) {
+            case "Message":
+                break;
+            case "Join":
+                this.hands.set(user_action.username, []);
+                this.scores.set(user_action.username, 0);
+                this.redraw_players();
+                break;
+            case "Leave":
+                this.hands.delete(user_action.username);
+                this.scores.delete(user_action.username);
+                this.redraw_players();
+                break;
+            case "NextToDeal":
+                this.dealer = user_action.username;
+                for (const card of this.community) {
+                    this.svg.removeChild(card.image);
+                }
+                for (const card of this.box) {
+                    this.svg.removeChild(card.image);
+                }
+                this.community = [];
+                this.box = [];
+                for (const [username, hand] of this.hands) {
+                    for (const card of hand) {
+                        this.svg.removeChild(card.image);
+                    }
+                }
+                this.hands.clear();
+                this.active = this.player_after(user_action.username);
+                this.redraw_players();
+                break;
+            case "ReceiveCard":
+                const card = {
+                    card: user_action.action.card,
+                    image: this.card_image(user_action.username, user_action.action.card),
+                };
+                if (!this.hands.has(user_action.username)) {
+                    this.hands.set(user_action.username, []);
+                }
+                this.hands.get(user_action.username).push(card);
+                this.redraw_cards();
+                break;
+            case "Score":
+                if (!this.scores.has(user_action.username)) {
+                    this.scores.set(user_action.username, 0);
+                }
+                this.set_info_text(user_action.username + " scores " + user_action.action.points + ": " + user_action.action.reason);
+                this.scores.set(user_action.username, this.scores.get(user_action.username) + user_action.action.points);
+                if ([...this.hands.values()].every(hand => hand.length === 0)) {
+                    this.hands = this.played;
+                    this.played = new Map();
+                    for (const [username, hand] of this.hands) {
+                        for (const card of hand) {
+                            this.svg.removeChild(card.image);
+                            card.card = null;
+                            card.image = this.card_image(username, null);
+                        }
+                    }
+                    this.redraw_cards();
+                }
+                this.redraw_players();
+                break;
+            case "PlayCard":
+                const played = this.remove_card(user_action.username, user_action.action.card);
+                if (played !== undefined) {
+                    this.svg.removeChild(played.image);
+                    if (!this.played.has(user_action.username)) {
+                        this.played.set(user_action.username, []);
+                    }
+                    this.played.get(user_action.username).push({
+                        card: user_action.action.card,
+                        image:this.card_image(user_action.username, user_action.action.card),
+                    });
+                }
+                /* fallthrough */
+            case "Pass":
+                this.active = user_action.username;
+                do {
+                    this.active = this.player_after(this.active);
+                } while (this.active !== user_action.username && this.hands.has(this.active) && this.hands.get(this.active).length === 0);
+                this.redraw_cards();
+                this.redraw_players();
+                break;
+            case "RevealCard":
+                this.reveal_card(user_action.username, user_action.action.card);
+                this.redraw_cards();
+                break;
+            case "PutInBox":
+                const removed = this.remove_card(user_action.username, user_action.action.card);
+                if (removed !== undefined) {
+                    this.svg.removeChild(removed.image);
+                    this.box.push({card: null, image: this.card_image(null, null)});
+                }
+                this.redraw_cards();
+                break;
+            case "CommunityCard":
+                const turnup = {
+                    card: user_action.action.card,
+                    image: this.card_image(null, user_action.action.card),
+                };
+                this.community.push(turnup);
+                this.active = this.player_after(this.dealer);
+                this.redraw_cards();
+                this.redraw_players();
+                break;
+            case "EndDeal":
+                this.active = user_action.username;
+                this.redraw_players();
+                break;
+            case "WinGame":
+                this.active = null;
+                this.redraw_cards();
+                this.redraw_players();
+                new CongratulateWinner(this.svg, user_action.username);
+                break;
+            default:
+                console.error("Unhandled action for cribbage", user_action);
+                this.set_error_text("Unhandled action for cribbage: " + user_action.action.action);
+                break;
+        }
+    }
+}
diff --git a/site/modules/game.js b/site/modules/game.js
new file mode 100644 (file)
index 0000000..31a0eb0
--- /dev/null
@@ -0,0 +1,68 @@
+export class Game {
+    constructor(container, summary, actions, username, send, close) {
+        this.container = container;
+        this.summary = summary;
+        this.username = username;
+        this.send = send;
+        this.seats = new Map();
+    }
+
+    take_initial_actions(actions) {
+        for (const user_action of actions) {
+            this.take_action(user_action);
+        }
+
+        if (!this.seats.has(this.username)) {
+            this.send({type: "TakeAction", action: {action: "Join", seat: 0, chips: 0}});
+        }
+    }
+
+    player_angle(seat) {
+        const my_seat = this.seats.get(this.username) || 0;
+        const num_seats = Math.max(...this.seats.values()) + 1;
+        const rel_seat = my_seat > seat ? num_seats + seat - my_seat : seat - my_seat;
+        if (num_seats % 4 === 0) {
+            if (rel_seat < num_seats / 4) {
+                return rel_seat * 2 * Math.PI / (num_seats + 2);
+            } else if (rel_seat >= num_seats / 4 && rel_seat <= 3 * num_seats / 4) {
+                return (rel_seat + 1) * 2 * Math.PI / (num_seats + 2);
+            } else {
+                return (rel_seat + 2) * 2 * Math.PI / (num_seats + 2);
+            }
+        } else {
+            return rel_seat * 2 * Math.PI / num_seats;
+        }
+    }
+
+    player_after(username) {
+        const in_seat = this.seats.get(username);
+        let [player_after, player_after_seat] = [username, null];
+        for (const [username, seat] of this.seats) {
+            if (seat > in_seat && (player_after_seat === null || seat < player_after_seat)) {
+                player_after = username;
+                player_after_seat = seat;
+            }
+        }
+        if (player_after_seat === null) for (const [username, seat] of this.seats) {
+            if (player_after_seat === null || seat < player_after_seat) {
+                player_after = username;
+                player_after_seat = seat;
+            }
+        }
+        return player_after;
+    }
+
+
+    take_action(user_action) {
+        switch (user_action.action.action) {
+            case "Join":
+                this.seats.set(user_action.username, user_action.action.seat);
+                break;
+            case "Leave":
+                this.seats.delete(user_action.username);
+                break;
+            default:
+                break;
+        }
+    }
+}
diff --git a/site/modules/gamechat.js b/site/modules/gamechat.js
new file mode 100644 (file)
index 0000000..14eb32c
--- /dev/null
@@ -0,0 +1,74 @@
+import { create_svg_element } from "./svg.js";
+import { Game } from "./game.js";
+import { Chatroom } from "./chatroom.js";
+
+export class GameWithChat extends Game {
+    constructor(container, summary, actions, username, send, close) {
+        super(container, summary, actions, username, send, close);
+
+        this.svg = create_svg_element(container, "svg", [], [["viewBox", "0 0 500 500"]]);
+        this.background = create_svg_element(this.svg, "rect", [], [["width", "500"], ["height", "500"], ["fill", "#404040"]]);
+
+        this.error_text_timeout = null;
+        this.error = create_svg_element(this.svg, "text", ["game-error"], [["x", "20"], ["y", "450"]]);
+        this.error_lines = [];
+        this.error_text = [];
+        const num_error_lines = 2;
+        for (let i = 0; i < num_error_lines; i++) {
+            const line = create_svg_element(this.error, "tspan", [], [["x", "20"], ["dy", "20"]]);
+            const text = document.createTextNode("");
+            line.append(text);
+            this.error_lines.push(line);
+            this.error_text.push(text);
+        }
+
+        this.close_control = create_svg_element(this.svg, "rect", ["close-control"], [["x", "460"], ["y", "5"], ["width", "35"], ["height", "35"], ["rx", "2"]])
+        this.close_control.onclick = close;
+        this.close_image = create_svg_element(this.svg, "image", ["close-image"], [["x", "465"], ["y", "10"], ["width", "25"], ["height", "25"], ["href", "img/close.svg"]]);
+
+        this.chat_control = create_svg_element(this.svg, "rect", ["chat-control"], [["x", "420"], ["y", "5"], ["width", "35"], ["height", "35"], ["rx", "2"]])
+        this.chat_control.onclick = () => this.chatroom_container.classList.toggle("hidden");
+        this.chat_image = create_svg_element(this.svg, "image", ["chat-image"], [["x", "425"], ["y", "10"], ["width", "25"], ["height", "25"], ["href", "img/chat.svg"]]);
+
+        this.chatroom_container = document.createElement("div");
+        this.chatroom_container.classList.add("embedded-chatroom");
+        const send_chat_messages_only = message => {
+            if (message.type === "TakeAction" && message.action.action === "Message") {
+                send(message);
+            }
+        };
+        this.chatroom = new Chatroom(this.chatroom_container, summary, [], username, send_chat_messages_only, null);
+        this.container.append(this.chatroom_container);
+    }
+
+    set_text(style, text) {
+        this.error.setAttribute("class", style);
+        const lines = text.split("\n", this.error_text.length);
+        for (const text of this.error_text) {
+            text.textContent = "";
+        }
+        for (let i = 0; i < lines.length; i++) {
+            this.error_text[i + this.error_text.length - lines.length].textContent = lines[i];
+        }
+        if (this.error_text_timeout !== null) clearTimeout(this.error_text_timeout);
+        this.error_text_timeout = setTimeout(() => {
+            for (const text of this.error_text) {
+                text.textContent = "";
+            }
+            this.error_text_timeout = null;
+        }, 5000);
+    }
+
+    set_info_text(text) {
+        this.set_text("game-info", text);
+    }
+
+    set_error_text(text) {
+        this.set_text("game-error", text);
+    }
+
+    take_action(user_action) {
+        super.take_action(user_action);
+        this.chatroom.take_action(user_action);
+    }
+}
index c9f401e0a539be91afd2ad8fcd44175e6d626701..d90322b513ac34d052d1681c3677d6ac938ea069 100644 (file)
@@ -187,7 +187,7 @@ export class GameList {
         format_row.append(format_title);
         const format_value = document.createElement("td");
         const format_input = document.createElement("select");
-        for (const format of ["KnockOutWhist", "TexasHoldEm", "Chatroom"]) {
+        for (const format of ["KnockOutWhist", "Cribbage", "TexasHoldEm", "Chatroom"]) {
             const game_format = document.createElement("option");
             game_format.setAttribute("value", format);
             game_format.innerText = format;
@@ -206,13 +206,14 @@ export class GameList {
         table.append(format_row);
 
         const fields = [
-            { name: "start_time", display: "Start Time", value: "", type: "datetime", formats: ["TexasHoldEm", "KnockOutWhist"] },
-            { name: "max_players", display: "Max Players", value: 2, type: "number", formats: ["TexasHoldEm", "KnockOutWhist"] },
+            { name: "start_time", display: "Start Time", value: "", type: "datetime", formats: ["TexasHoldEm", "KnockOutWhist", "Cribbage"] },
+            { name: "max_players", display: "Max Players", value: 2, type: "number", formats: ["TexasHoldEm", "KnockOutWhist", "Cribbage"] },
             { name: "small_blind", display: "Small Blind", value: 25, type: "number", formats: ["TexasHoldEm"] },
             { name: "starting_stack", display: "Starting Stack", value: 5000, step: 1000, type: "number", formats: ["TexasHoldEm"] },
             { name: "round_length", display: "Round Length", value: "", type: "duration", formats: ["TexasHoldEm"] },
             { name: "tournament_length", display: "Tournament Length", value: "", type: "duration", formats: ["TexasHoldEm"] },
             { name: "action_timeout", display: "Action Timeout", value: "", type: "duration", formats: ["TexasHoldEm"] },
+            { name: "target_score", display: "Target Score", value: 121, type: "number", formats: ["Cribbage"] },
         ];
 
         for (const {name, display, value, type, step, formats} of fields) {
index 4c75b8e088c1717f15d360562559ea7eec31f622..605a8e6e7609c91ef6b8a6edfd597d3332f51f64 100644 (file)
@@ -30,6 +30,17 @@ export class MainMenu {
         knock_out_whist.onclick = () => this.send({type: "JoinLobby", filter: "format: KnockOutWhist and created_in_last: 2:00:00"});
         menu_container.append(knock_out_whist);
 
+        const cribbage = document.createElement("div");
+        cribbage.classList.add("game-format-icon");
+        const cribbage_img = document.createElement("img");
+        cribbage_img.setAttribute("src", "img/cribbage.svg");
+        const cribbage_text = document.createElement("p");
+        cribbage_text.innerText = "Cribbage";
+        cribbage.append(cribbage_img);
+        cribbage.append(cribbage_text);
+        cribbage.onclick = () => this.send({type: "JoinLobby", filter: "format: Cribbage and created_in_last: 2:00:00"});
+        menu_container.append(cribbage);
+
         const chatroom = document.createElement("div");
         chatroom.classList.add("game-format-icon");
         const chatroom_img = document.createElement("img");
index c5c75f1de05d3129271b765d3277fa8c08eda225..a645a7a04ee8db13fadfc9f3196e51f3432a0c0c 100644 (file)
@@ -1,25 +1,8 @@
-const svgns = "http://www.w3.org/2000/svg";
-
+import { svgns, create_svg_element } from "./svg.js";
 import { card_href, suit_href } from "./card.js";
 import { Chatroom } from "./chatroom.js";
 import { CongratulateWinner } from "./winner.js";
 
-function set_element_attributes(element, attributes) {
-    for (const [attr, value] of attributes) {
-        element.setAttribute(attr, value);
-    }
-}
-
-function create_svg_element(parent, name, class_list, attributes) {
-    const element = document.createElementNS(svgns, name);
-    set_element_attributes(element, attributes);
-    for (const cls of class_list) {
-        element.classList.add(cls);
-    }
-    parent.append(element);
-    return element;
-}
-
 export class BetControls {
     constructor(game) {
         this.game = game
@@ -87,7 +70,7 @@ export class BetControls {
             if (chips !== null && !isNaN(Number(chips))) {
                 game.send({type: "TakeAction", action: {action: "Bet", chips: Number(chips)}});
             }
-        }
+        };
 
         this.bet_control_label = create_svg_element(this.game.svg, "text", ["bet-control-label"], [["x", "380"], ["y", "566"]]);
         this.bet_control_text = document.createTextNode("Bet");
@@ -165,11 +148,11 @@ export class TexasHoldEm {
 
         this.close_control = create_svg_element(this.svg, "rect", ["close-control"], [["x", "460"], ["y", "5"], ["width", "35"], ["height", "35"], ["rx", "2"]])
         this.close_control.onclick = close;
-        this.close_image = create_svg_element(this.svg, "image", ["close-image"], [["x", "465"], ["y", "10"], ["width", "25"], ["height", "25"], ["href", "img/close.svg"]])
+        this.close_image = create_svg_element(this.svg, "image", ["close-image"], [["x", "465"], ["y", "10"], ["width", "25"], ["height", "25"], ["href", "img/close.svg"]]);
 
         this.chat_control = create_svg_element(this.svg, "rect", ["chat-control"], [["x", "420"], ["y", "5"], ["width", "35"], ["height", "35"], ["rx", "2"]])
         this.chat_control.onclick = () => this.chatroom_container.classList.toggle("hidden");
-        this.chat_image = create_svg_element(this.svg, "image", ["chat-image"], [["x", "425"], ["y", "10"], ["width", "25"], ["height", "25"], ["href", "img/chat.svg"]])
+        this.chat_image = create_svg_element(this.svg, "image", ["chat-image"], [["x", "425"], ["y", "10"], ["width", "25"], ["height", "25"], ["href", "img/chat.svg"]]);
 
         this.chatroom_container = document.createElement("div");
         this.chatroom_container.classList.add("embedded-chatroom");
index b10cb318efc00fc5e00640755b6e21e11b92b033..83fb3c04440818cfe0de13406f109f0a39c38eaa 100644 (file)
@@ -1,9 +1,8 @@
-const svgns = "http://www.w3.org/2000/svg";
-
 import { Auth } from "./auth.js";
 import { MainMenu } from "./mainmenu.js";
 import { GameList } from "./gamelist.js";
 import { Chatroom } from "./chatroom.js";
+import { Cribbage } from "./cribbage.js";
 import { KnockOutWhist } from "./whist.js";
 import { TexasHoldEm } from "./poker.js";
 
@@ -128,6 +127,7 @@ export class Socket {
                     this.send({type: "TakeAction", action: {action: "Join", seat: message.action.action.seat + 1, chips: message.action.action.chips}});
                 } else {
                     console.error("Taking action failed: " + message.reason, message.action);
+                    console.error("This.game: ", this.game);
                     this.game.set_error_text(message.reason);
                 }
                 break;
@@ -183,6 +183,7 @@ export class Socket {
                 return 1000;
             case "RevealCard":
                 return 1000;
+            case "Score":
             case "CutCard":
                 return 3000;
             case "PlayCard":
@@ -301,6 +302,7 @@ export class Socket {
         let Format;
         switch (this.game.summary.settings.format) {
             case "Chatroom": Format = Chatroom; break;
+            case "Cribbage": Format = Cribbage; break;
             case "KnockOutWhist": Format = KnockOutWhist; break;
             case "TexasHoldEm": Format = TexasHoldEm; break;
             default:
diff --git a/site/modules/svg.js b/site/modules/svg.js
new file mode 100644 (file)
index 0000000..a571542
--- /dev/null
@@ -0,0 +1,17 @@
+export const svgns = "http://www.w3.org/2000/svg";
+
+function set_element_attributes(element, attributes) {
+    for (const [attr, value] of attributes) {
+        element.setAttribute(attr, value);
+    }
+}
+
+export function create_svg_element(parent, name, class_list, attributes) {
+    const element = document.createElementNS(svgns, name);
+    set_element_attributes(element, attributes);
+    for (const cls of class_list) {
+        element.classList.add(cls);
+    }
+    parent.append(element);
+    return element;
+}
index 0a3170d35936d610e41856ac8b7f850f7654fc1a..f7b450386b591cd6a7ff293d0b5ad42a85cc3c8d 100644 (file)
@@ -1,5 +1,4 @@
-const svgns = "http://www.w3.org/2000/svg";
-
+import { svgns, create_svg_element } from "./svg.js";
 import { card_href, suit_href } from "./card.js";
 import { Chatroom } from "./chatroom.js";
 import { CongratulateWinner } from "./winner.js";
index 50bc0fb08a774d204c53cec4b25578dbf7e049a4..330a6de9c9240964bba062ba183b8f055331c523 100644 (file)
@@ -1,5 +1,4 @@
-const svgns = "http://www.w3.org/2000/svg";
-
+import { svgns, create_svg_element } from "./svg.js";
 import { card_href, FIFTY_TWO_CARD_DECK } from "./card.js";
 import { random_int } from "./random.js";
 
index 0c8645624c6d21843e01d39b529bd016302ead90..b614cf9b1dc2f2ce07476190c4ec3e648dabbd12 100644 (file)
@@ -3,6 +3,7 @@
 @import url("style/game-list.css");
 @import url("style/game.css");
 @import url("style/chatroom.css");
+@import url("style/cribbage.css");
 @import url("style/poker.css");
 @import url("style/whist.css");
 @import url("style/winner.css");
diff --git a/site/style/cribbage.css b/site/style/cribbage.css
new file mode 100644 (file)
index 0000000..8cb96f8
--- /dev/null
@@ -0,0 +1,22 @@
+.pass-control {
+    fill: #808080;
+    stroke: black;
+    stroke-width: 1px;
+    transition: fill 0.5s;
+    pointer-events: none;
+    cursor: pointer;
+}
+
+.pass-control.active {
+    fill: #8080ff;
+    pointer-events: auto;
+}
+
+.pass-control.active:hover {
+    fill: #a0a0ff;
+}
+
+.pass-control-label {
+    pointer-events: none;
+    text-anchor: middle;
+}
index 95b79a51acfe38fb38e6977e961585aae8b58da9..fa0e216103e48d3c86d07cfe50f3fadb1d3faead 100644 (file)
     display: table-row;
 }
 
+.game-popup > table.Cribbage tr.game-field {
+    display: none;
+}
+
+.game-popup > table.Cribbage tr.game-field.Cribbage {
+    display: table-row;
+}
+
 .game-popup input, .game-popup select {
     width: 100%;
     height: 100%;