From: Geoffrey Allott Date: Wed, 14 Jun 2023 10:28:31 +0000 (+0100) Subject: share more code between cribbage, whist and poker X-Git-Url: https://git.pointlesshacks.com/?a=commitdiff_plain;h=a064f3f509b53909dcc34d7ea058384e324ac4ea;p=pokerwave.git share more code between cribbage, whist and poker --- diff --git a/site/modules/betcontrols.js b/site/modules/betcontrols.js new file mode 100644 index 0000000..9231772 --- /dev/null +++ b/site/modules/betcontrols.js @@ -0,0 +1,89 @@ +import { create_svg_element } from "./svg.js"; + +export class BetControls { + constructor(game) { + this.game = game + this.mouse_clicked = false; + document.addEventListener("mousedown", () => this.mouse_clicked = true); + document.addEventListener("mouseup", () => this.mouse_clicked = false); + + this.slider_min = 120; + this.slider_max = 430; + + this.background = create_svg_element(this.game.svg, "rect", ["controls"], [["x", "50"], ["y", "500"], ["width", "400"], ["height", "96"], ["rx", "15"]]) + this.bet_size_area = create_svg_element(this.game.svg, "rect", ["bet-size-area"], [["x", "57"], ["y", "507"], ["width", "58"], ["height", "22"]]) + this.bet_size_area.onclick = () => { + const chips = prompt("Chips to bet", this.bet_size_text.textContent); + if (chips !== null && !isNaN(Number(chips))) { + const bet = Math.max(this.game.min_bet(), Math.min(this.game.all_in_bet(), Number(chips))); + this.bet_size_text.textContent = Number(chips); + const p = (bet - this.game.min_bet()) / (this.game.all_in_bet() - this.game.min_bet()); + const x = Math.max(this.slider_min, Math.min(this.slider_max, Math.sqrt(p) * (this.slider_max - this.slider_min) + this.slider_min)); + this.bet_slider_thumb.setAttribute("x", x); + } + }; + + this.bet_size = create_svg_element(this.game.svg, "text", ["bet-size"], [["x", "86"], ["y", "522"]]) + this.bet_size_text = document.createTextNode("150"); + this.bet_size.append(this.bet_size_text); + + const point = this.game.svg.createSVGPoint(); + const move_slider = e => { + point.x = e.clientX; + point.y = e.clientY; + const coords = point.matrixTransform(this.game.svg.getScreenCTM().inverse()); + const x = Math.max(this.slider_min, Math.min(this.slider_max, coords.x - 5)); + const p = Math.pow((x - this.slider_min) / (this.slider_max - this.slider_min), 2); + const bet = Math.round(this.game.min_bet() * (1 - p) + this.game.all_in_bet() * p); + this.bet_size_text.textContent = bet; + this.bet_slider_thumb.setAttribute("x", x); + }; + + this.bet_slider = create_svg_element(this.game.svg, "g", ["bet-slider"], []); + this.bet_slider_area = create_svg_element(this.bet_slider, "rect", ["bet-slider-area"], [["x", this.slider_min], ["y", "507"], ["width", this.slider_max - this.slider_min], ["height", "22"]]); + this.bet_slider_track = create_svg_element(this.bet_slider, "rect", ["bet-slider-track"], [["x", this.slider_min], ["y", "515"], ["width", this.slider_max - this.slider_min], ["height", "6"]]); + this.bet_slider_thumb = create_svg_element(this.bet_slider, "rect", ["bet-slider-thumb"], [["x", this.slider_min], ["y", "509"], ["width", "10"], ["height", "18"]]); + this.bet_slider_area.onmousedown = this.bet_slider_track.onmousedown = this.bet_slider_thumb.onmousedown = move_slider; + this.bet_slider_area.onmousemove = this.bet_slider_track.onmousemove = this.bet_slider_thumb.onmousemove = e => { if (this.mouse_clicked) move_slider(e); }; + this.bet_slider_area.ontouchmove = this.bet_slider_track.ontouchmove = this.bet_slider_thumb.ontouchmove = e => move_slider(e.touches.item(0)); + + this.fold_control = create_svg_element(this.game.svg, "rect", ["fold-control"], [["x", "60"], ["y", "536"], ["width", "120"], ["height", "50"], ["rx", "10"]]) + this.fold_control.onclick = () => this.game.send({type: "TakeAction", action: {action: "Fold"}}); + + this.fold_control_label = create_svg_element(this.game.svg, "text", ["fold-control-label"], [["x", "120"], ["y", "566"]]) + this.fold_control_text = document.createTextNode("Fold"); + this.fold_control_label.append(this.fold_control_text); + + this.call_control = create_svg_element(this.game.svg, "rect", ["call-control"], [["x", "190"], ["y", "536"], ["width", "120"], ["height", "50"], ["rx", "10"]]) + this.call_control.onclick = () => this.game.send({type: "TakeAction", action: {action: "Bet", chips: this.game.chips_to_call()}}); + + this.call_control_label = create_svg_element(this.game.svg, "text", ["call-control-label"], [["x", "250"], ["y", "566"]]); + this.call_control_text = document.createTextNode("Call"); + this.call_control_label.append(this.call_control_text); + + this.bet_control = create_svg_element(this.game.svg, "rect", ["bet-control"], [["x", "320"], ["y", "536"], ["width", "120"], ["height", "50"], ["rx", "10"]]) + this.bet_control.onclick = () => { + const chips = +this.bet_size_text.textContent; + 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"); + this.bet_control_label.append(this.bet_control_text); + } + + redraw() { + this.fold_control.classList.toggle("active", this.game.active === this.game.username && this.game.chips_to_call() > 0); + this.call_control.classList.toggle("active", this.game.active === this.game.username); + this.call_control_text.textContent = this.game.active === this.game.username && this.game.chips_to_call() == 0 ? "Check" : "Call"; + const can_bet = this.game.active === this.game.username && this.game.chips_to_call() < this.game.all_in_bet(); + this.bet_control.classList.toggle("active", can_bet); + this.bet_size_area.classList.toggle("active", can_bet); + this.bet_slider.classList.toggle("active", can_bet); + this.bet_size_text.textContent = this.game.min_bet(); + this.bet_slider_thumb.setAttribute("x", this.slider_min); + } +}; + diff --git a/site/modules/cribbage.js b/site/modules/cribbage.js index cfc6316..8cf3087 100644 --- a/site/modules/cribbage.js +++ b/site/modules/cribbage.js @@ -21,9 +21,6 @@ export class Cribbage extends GameWithChat { this.dealer = null; this.dealing = false; - const table = create_svg_element(this.svg, "ellipse", [], [["cx", "250"], ["cy", "253"], ["rx", "250"], ["ry", "110"], ["fill", "#604010"]]); - const felt = create_svg_element(this.svg, "ellipse", [], [["cx", "250"], ["cy", "250"], ["rx", "240"], ["ry", "100"], ["fill", "green"]]); - 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"}}); @@ -173,16 +170,6 @@ export class Cribbage extends GameWithChat { } } - next_active_player(username) { - console.log('Finding player after', username); - let active = username; - do { - active = this.player_after(active); - console.log('.. got ', active); - } while (active !== username && (this.passed.has(active) || this.hands.has(active) && this.hands.get(active).length === 0)); - return active; - } - clear_pegging() { this.count = 0; this.passed.clear(); @@ -229,7 +216,7 @@ export class Cribbage extends GameWithChat { this.hands.clear(); this.played.clear(); this.passed.clear(); - this.active = this.player_after(user_action.username); + this.active = this.player_after(user_action.username, _ => true); this.redraw_players(); break; case "ReceiveCard": @@ -265,9 +252,6 @@ export class Cribbage extends GameWithChat { break; case "PlayCard": this.count += value_ace_low(user_action.action.card.rank); - if (this.count === 31) { - this.clear_pegging(); - } const played = this.remove_card(user_action.username, user_action.action.card); if (played !== undefined) { this.svg.removeChild(played.image); @@ -279,7 +263,10 @@ export class Cribbage extends GameWithChat { image:this.card_image(user_action.username, user_action.action.card), }); } - this.active = this.next_active_player(user_action.username); + if (this.count === 31) { + this.clear_pegging(); + } + this.active = this.player_after(user_action.username, username => !this.passed.has(username) || this.hands.has(username) && this.hands.get(username).length > 0); this.redraw_cards(); this.redraw_players(); break; @@ -288,7 +275,7 @@ export class Cribbage extends GameWithChat { if (this.passed.size === this.hands.size) { this.clear_pegging(); } - this.active = this.next_active_player(user_action.username); + this.active = this.player_after(user_action.username, username => !this.passed.has(username) || this.hands.has(username) && this.hands.get(username).length > 0); this.redraw_cards(); this.redraw_players(); break; @@ -314,7 +301,7 @@ export class Cribbage extends GameWithChat { image: this.card_image(null, user_action.action.card), }; this.community.push(turnup); - this.active = this.player_after(this.dealer); + this.active = this.player_after(this.dealer, _ => true); this.redraw_cards(); this.redraw_players(); break; diff --git a/site/modules/game.js b/site/modules/game.js index 31a0eb0..33564d0 100644 --- a/site/modules/game.js +++ b/site/modules/game.js @@ -13,7 +13,8 @@ export class Game { } if (!this.seats.has(this.username)) { - this.send({type: "TakeAction", action: {action: "Join", seat: 0, chips: 0}}); + const chips = this.summary.settings.starting_stack || 0; + this.send({type: "TakeAction", action: {action: "Join", seat: 0, chips: chips}}); } } @@ -34,17 +35,17 @@ export class Game { } } - player_after(username) { + player_after(username, is_playing) { 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)) { + if (is_playing(username) && 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) { + if (is_playing(username) && player_after_seat === null || seat < player_after_seat) { player_after = username; player_after_seat = seat; } diff --git a/site/modules/gamechat.js b/site/modules/gamechat.js index f37c764..7758751 100644 --- a/site/modules/gamechat.js +++ b/site/modules/gamechat.js @@ -7,8 +7,8 @@ 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.svg = create_svg_element(container, "svg", [], [["viewBox", "0 0 500 600"]]); + this.background = create_svg_element(this.svg, "rect", [], [["width", "500"], ["height", "600"], ["fill", "#404040"]]); this.error_text_timeout = null; this.error = create_svg_element(this.svg, "text", ["game-error"], [["x", "20"], ["y", "450"]]); @@ -31,6 +31,9 @@ export class GameWithChat extends Game { 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"]]); + const table = create_svg_element(this.svg, "ellipse", [], [["cx", "250"], ["cy", "253"], ["rx", "250"], ["ry", "110"], ["fill", "#604010"]]); + const felt = create_svg_element(this.svg, "ellipse", [], [["cx", "250"], ["cy", "250"], ["rx", "240"], ["ry", "100"], ["fill", "green"]]); + this.chatroom_container = document.createElement("div"); this.chatroom_container.classList.add("embedded-chatroom"); const send_chat_messages_only = message => { diff --git a/site/modules/poker.js b/site/modules/poker.js index a645a7a..7a66149 100644 --- a/site/modules/poker.js +++ b/site/modules/poker.js @@ -1,103 +1,14 @@ -import { svgns, create_svg_element } from "./svg.js"; +import { create_svg_element } from "./svg.js"; import { card_href, suit_href } from "./card.js"; -import { Chatroom } from "./chatroom.js"; +import { GameWithChat } from "./gamechat.js"; +import { BetControls } from "./betcontrols.js"; import { CongratulateWinner } from "./winner.js"; -export class BetControls { - constructor(game) { - this.game = game - this.mouse_clicked = false; - document.addEventListener("mousedown", () => this.mouse_clicked = true); - document.addEventListener("mouseup", () => this.mouse_clicked = false); - - this.slider_min = 120; - this.slider_max = 430; - - this.background = create_svg_element(this.game.svg, "rect", ["controls"], [["x", "50"], ["y", "500"], ["width", "400"], ["height", "96"], ["rx", "15"]]) - this.bet_size_area = create_svg_element(this.game.svg, "rect", ["bet-size-area"], [["x", "57"], ["y", "507"], ["width", "58"], ["height", "22"]]) - this.bet_size_area.onclick = () => { - const chips = prompt("Chips to bet", this.bet_size_text.textContent); - if (chips !== null && !isNaN(Number(chips))) { - const bet = Math.max(this.game.min_bet(), Math.min(this.game.all_in_bet(), Number(chips))); - this.bet_size_text.textContent = Number(chips); - const p = (bet - this.game.min_bet()) / (this.game.all_in_bet() - this.game.min_bet()); - const x = Math.max(this.slider_min, Math.min(this.slider_max, Math.sqrt(p) * (this.slider_max - this.slider_min) + this.slider_min)); - this.bet_slider_thumb.setAttribute("x", x); - } - }; - - this.bet_size = create_svg_element(this.game.svg, "text", ["bet-size"], [["x", "86"], ["y", "522"]]) - this.bet_size_text = document.createTextNode("150"); - this.bet_size.append(this.bet_size_text); - - const point = this.game.svg.createSVGPoint(); - const move_slider = e => { - point.x = e.clientX; - point.y = e.clientY; - const coords = point.matrixTransform(this.game.svg.getScreenCTM().inverse()); - const x = Math.max(this.slider_min, Math.min(this.slider_max, coords.x - 5)); - const p = Math.pow((x - this.slider_min) / (this.slider_max - this.slider_min), 2); - const bet = Math.round(this.game.min_bet() * (1 - p) + this.game.all_in_bet() * p); - this.bet_size_text.textContent = bet; - this.bet_slider_thumb.setAttribute("x", x); - }; - - this.bet_slider = create_svg_element(this.game.svg, "g", ["bet-slider"], []); - this.bet_slider_area = create_svg_element(this.bet_slider, "rect", ["bet-slider-area"], [["x", String(this.slider_min)], ["y", "507"], ["width", String(this.slider_max - this.slider_min)], ["height", "22"]]) - this.bet_slider_track = create_svg_element(this.bet_slider, "rect", ["bet-slider-track"], [["x", String(this.slider_min)], ["y", "515"], ["width", String(this.slider_max - this.slider_min)], ["height", "6"]]) - this.bet_slider_thumb = create_svg_element(this.bet_slider, "rect", ["bet-slider-thumb"], [["x", String(this.slider_min)], ["y", "509"], ["width", "10"], ["height", "18"]]) - this.bet_slider_area.onmousedown = this.bet_slider_track.onmousedown = this.bet_slider_thumb.onmousedown = move_slider; - this.bet_slider_area.onmousemove = this.bet_slider_track.onmousemove = this.bet_slider_thumb.onmousemove = e => { if (this.mouse_clicked) move_slider(e); }; - this.bet_slider_area.ontouchmove = this.bet_slider_track.ontouchmove = this.bet_slider_thumb.ontouchmove = e => move_slider(e.touches.item(0)); - - this.fold_control = create_svg_element(this.game.svg, "rect", ["fold-control"], [["x", "60"], ["y", "536"], ["width", "120"], ["height", "50"], ["rx", "10"]]) - this.fold_control.onclick = () => this.game.send({type: "TakeAction", action: {action: "Fold"}}); - - this.fold_control_label = create_svg_element(this.game.svg, "text", ["fold-control-label"], [["x", "120"], ["y", "566"]]) - this.fold_control_text = document.createTextNode("Fold"); - this.fold_control_label.append(this.fold_control_text); - - this.call_control = create_svg_element(this.game.svg, "rect", ["call-control"], [["x", "190"], ["y", "536"], ["width", "120"], ["height", "50"], ["rx", "10"]]) - this.call_control.onclick = () => this.game.send({type: "TakeAction", action: {action: "Bet", chips: this.game.chips_to_call()}}); - - this.call_control_label = create_svg_element(this.game.svg, "text", ["call-control-label"], [["x", "250"], ["y", "566"]]); - this.call_control_text = document.createTextNode("Call"); - this.call_control_label.append(this.call_control_text); - - this.bet_control = create_svg_element(this.game.svg, "rect", ["bet-control"], [["x", "320"], ["y", "536"], ["width", "120"], ["height", "50"], ["rx", "10"]]) - this.bet_control.onclick = () => { - const chips = +this.bet_size_text.textContent; - 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"); - this.bet_control_label.append(this.bet_control_text); - } - - redraw() { - this.fold_control.classList.toggle("active", this.game.active === this.game.username && this.game.chips_to_call() > 0); - this.call_control.classList.toggle("active", this.game.active === this.game.username); - this.call_control_text.textContent = this.game.active === this.game.username && this.game.chips_to_call() == 0 ? "Check" : "Call"; - const can_bet = this.game.active === this.game.username && this.game.chips_to_call() < this.game.all_in_bet(); - this.bet_control.classList.toggle("active", can_bet); - this.bet_size_area.classList.toggle("active", can_bet); - this.bet_slider.classList.toggle("active", can_bet); - this.bet_size_text.textContent = this.game.min_bet(); - this.bet_slider_thumb.setAttribute("x", this.slider_min); - } -}; - -export class TexasHoldEm { +export class TexasHoldEm extends GameWithChat { constructor(container, summary, actions, username, send, close) { - this.container = container; - this.summary = summary; - this.actions = actions; - this.username = username; - this.send = send; - this.seats = new Map(); + super(container, summary, actions, username, send, close); + this.svg.classList.add("texas-hold-em"); + this.stacks = new Map(); this.hands = new Map(); this.bets = new Map(); @@ -107,30 +18,6 @@ export class TexasHoldEm { this.user_icons = new Map(); this.small_blind = this.summary.settings.small_blind; this.big_blind = this.small_blind * 2; - this.error_text_timeout = null; - - this.svg = create_svg_element(this.container, "svg", ["texas-hold-em"], [["viewBox", "0 0 500 600"]]) - - const background = create_svg_element(this.svg, "rect", [], [["width", "500"], ["height", "600"], ["fill", ["#404040"]]]); - - 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 = document.createElementNS(svgns, "tspan"); - const text = document.createTextNode(""); - line.setAttribute("x", "20"); - line.setAttribute("dy", "20"); - line.append(text); - this.error.append(line); - this.error_lines.push(line); - this.error_text.push(text); - } - - const table = create_svg_element(this.svg, "ellipse", [], [["cx", "250"], ["cy", "253"], ["rx", "250"], ["ry", "110"], ["fill", "#604010"]]); - const felt = create_svg_element(this.svg, "ellipse", [], [["cx", "250"], ["cy", "250"], ["rx", "240"], ["ry", "100"], ["fill", "green"]]); const pot_size = create_svg_element(this.svg, "text", ["pot-size"], [["x", "400"], ["y", "250"]]); this.pot_size_text = document.createTextNode(""); @@ -146,72 +33,7 @@ export class TexasHoldEm { this.bet_controls = new BetControls(this) - 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); - - for (const user_action of this.actions) { - this.take_action(user_action); - } - - if (!this.seats.has(this.username)) { - this.send({type: "TakeAction", action: {action: "Join", seat: 0, chips: this.summary.settings.starting_stack}}); - } - } - - 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); - } - - player_angle(seat) { - const rel_seat = this.my_seat > seat ? this.num_seats + seat - this.my_seat : seat - this.my_seat; - if (this.num_seats % 4 === 0) { - if (rel_seat < this.num_seats / 4) { - return rel_seat * 2 * Math.PI / (this.num_seats + 2); - } else if (rel_seat >= this.num_seats / 4 && rel_seat <= 3 * this.num_seats / 4) { - return (rel_seat + 1) * 2 * Math.PI / (this.num_seats + 2); - } else { - return (rel_seat + 2) * 2 * Math.PI / (this.num_seats + 2); - } - } else { - return rel_seat * 2 * Math.PI / this.num_seats; - } + this.take_initial_actions(actions); } redraw_players() { @@ -255,37 +77,23 @@ export class TexasHoldEm { } for (const [username, seat] of this.seats) { if (!this.user_icons.has(username)) { - const user = document.createElementNS(svgns, "text"); - const text = document.createTextNode(username); - user.append(text); const angle = this.player_angle(seat); const x = 240 - 180 * Math.sin(angle); const y = 240 + 120 * Math.cos(angle) + 60 * (Math.sign(Math.cos(angle)) || 1); - user.setAttribute("x", x); - user.setAttribute("y", y); - this.svg.append(user); + const user = create_svg_element(this.svg, "text", [], [["x", x], ["y", y]]); + const text = document.createTextNode(username); + user.append(text); - const stack = document.createElementNS(svgns, "text"); + const stack = create_svg_element(this.svg, "text", [], [["x", x], ["y", y + 20]]); const stack_text = document.createTextNode(this.stacks.get(username)); stack.append(stack_text); - stack.setAttribute("x", x); - stack.setAttribute("y", y + 20); - this.svg.append(stack); - const active = document.createElementNS(svgns, "circle"); - active.setAttribute("cx", x - 10); - active.setAttribute("cy", y - 5); - active.setAttribute("r", 5); - active.classList.add("active-indicator"); + const active = create_svg_element(this.svg, "circle", ["active-indicator"], [["cx", x - 10], ["cy", y - 5], ["r", 5]]); active.classList.toggle("active", this.active === username); - this.svg.append(active); - const bet = document.createElementNS(svgns, "text"); + const bet = create_svg_element(this.svg, "text", [], [["x", 240 - 120 * Math.sin(angle)], ["y", 250 + 70 * Math.cos(angle)]]); const bet_text = document.createTextNode(this.bets.get(username) || ""); bet.append(bet_text); - bet.setAttribute("x", 240 - 120 * Math.sin(angle)); - bet.setAttribute("y", 250 + 70 * Math.cos(angle)); - this.svg.append(bet); this.user_icons.set(username, [user, stack, active, bet]); } @@ -315,30 +123,7 @@ export class TexasHoldEm { } card_image(username, card) { - const image = document.createElementNS(svgns, card === null ? "image" : "use"); - image.setAttribute("width", "45"); - image.setAttribute("height", "70"); - image.setAttribute("href", card_href(card)); - this.svg.append(image); - return image; - } - - player_after(username, is_playing) { - const in_seat = this.seats.get(username); - let [player_after, player_after_seat] = [null, null]; - for (const [username, seat] of this.seats) { - if (is_playing(username) && 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 (is_playing(username) && (player_after_seat === null || seat < player_after_seat)) { - player_after = username; - player_after_seat = seat; - } - } - return player_after; + return create_svg_element(this.svg, card === null ? "image" : "use", [], [["width", "45"], ["height", "70"], ["href", card_href(card)]]); } chips_to_call() { @@ -388,25 +173,19 @@ export class TexasHoldEm { } take_action(user_action) { - this.chatroom.take_action(user_action); + super.take_action(user_action); switch (user_action.action.action) { case "Message": break; case "Join": - this.seats.set(user_action.username, user_action.action.seat); this.stacks.set(user_action.username, user_action.action.chips); this.hands.set(user_action.username, []); - this.num_seats = Math.max(...this.seats.values()) + 1; - this.my_seat = this.seats.get(this.username) || 0; this.redraw_players(); break; case "Leave": - this.seats.delete(user_action.username); this.stacks.delete(user_action.username); this.hands.delete(user_action.username); this.bets.delete(user_action.username); - this.num_seats = Math.max(...this.seats.values()) + 1; - this.my_seat = this.seats.get(this.username) || 0; this.redraw_players(); break; case "KnockedOut": @@ -418,8 +197,8 @@ export class TexasHoldEm { } this.hands.delete(user_action.username); this.bets.delete(user_action.username); - const small_blind = this.player_after(this.dealer, () => true); - const big_blind = this.player_after(small_blind, () => true); + const small_blind = this.player_after(this.dealer, _ => true); + const big_blind = this.player_after(small_blind, _ => true); if (this.stacks.length <= 1) { this.seats.delete(user_action.username); } else if (user_action.username === this.dealer) { @@ -530,7 +309,7 @@ export class TexasHoldEm { this.redraw_players(); break; case "EndDeal": - this.active = this.player_after(user_action.username, username => true); + this.active = this.player_after(user_action.username, _ => true); this.redraw_players(); break; case "WinGame": diff --git a/site/modules/whist.js b/site/modules/whist.js index f7b4503..8969fd9 100644 --- a/site/modules/whist.js +++ b/site/modules/whist.js @@ -1,194 +1,32 @@ import { svgns, create_svg_element } from "./svg.js"; import { card_href, suit_href } from "./card.js"; -import { Chatroom } from "./chatroom.js"; +import { GameWithChat } from "./gamechat.js"; import { CongratulateWinner } from "./winner.js"; -export class KnockOutWhist { +export class KnockOutWhist extends GameWithChat { constructor(container, summary, actions, username, send, close) { - this.container = container; - this.summary = summary; - this.username = username; - this.send = send; - this.seats = new Map(); + super(container, summary, actions, username, send, close); + this.svg.classList.add("knock-out-whist"); + this.hands = new Map(); this.tricks_won = new Map(); this.community = []; this.trick = []; this.user_icons = new Map(); - this.error_text_timeout = null; - - this.svg = document.createElementNS(svgns, "svg"); - this.svg.classList.add("knock-out-whist"); - this.svg.setAttribute("viewBox", "0 0 500 500"); - - const background = document.createElementNS(svgns, "rect"); - background.setAttribute("width", "500"); - background.setAttribute("height", "500"); - background.setAttribute("fill", "#404040"); - this.svg.append(background); - this.error = document.createElementNS(svgns, "text"); - this.error.setAttribute("x", "20"); - this.error.setAttribute("y", "450"); - this.error.classList.add("game-error"); - this.error_lines = []; - this.error_text = []; - const num_error_lines = 2; - for (let i = 0; i < num_error_lines; i++) { - const line = document.createElementNS(svgns, "tspan"); - const text = document.createTextNode(""); - line.setAttribute("x", "20"); - line.setAttribute("dy", "20"); - line.append(text); - this.error.append(line); - this.error_lines.push(line); - this.error_text.push(text); - } - this.svg.append(this.error); - - const table = document.createElementNS(svgns, "ellipse"); - table.setAttribute("cx", "250"); - table.setAttribute("cy", "253"); - table.setAttribute("rx", "250"); - table.setAttribute("ry", "110"); - table.setAttribute("fill", "#604010"); - this.svg.append(table); - - const felt = document.createElementNS(svgns, "ellipse"); - felt.setAttribute("cx", "250"); - felt.setAttribute("cy", "250"); - felt.setAttribute("rx", "240"); - felt.setAttribute("ry", "100"); - felt.setAttribute("fill", "green"); - this.svg.append(felt); - - const suits = document.createElementNS(svgns, "rect"); - suits.setAttribute("x", "340"); - suits.setAttribute("y", "235"); - suits.setAttribute("width", "100"); - suits.setAttribute("height", "30"); - suits.setAttribute("rx", "10"); - suits.setAttribute("fill", "darkgreen"); - this.svg.append(suits); + const suits = create_svg_element(this.svg, "rect", [], [["x", "340"], ["y", "235"], ["width", "100"], ["height", "30"], ["rx", "10"], ["fill", "darkgreen"]]); this.glyphs = new Map(); let x = 340; for (const suit of ["Clubs", "Diamonds", "Hearts", "Spades"]) { - const highlight = document.createElementNS(svgns, "circle"); - highlight.setAttribute("cx", x + 12.5); - highlight.setAttribute("cy", "250"); - highlight.setAttribute("r", "10"); - highlight.classList.add("suit-highlight"); + const highlight = create_svg_element(this.svg, "circle", ["suit-highlight"], [["cx", x + 12.5], ["cy", "250"], ["r", "10"]]); highlight.onclick = () => this.send({type: "TakeAction", action: {action: "ChooseTrumps", suit: suit}}); - this.svg.append(highlight); this.glyphs.set(suit, highlight); - const glyph = document.createElementNS(svgns, "use"); - glyph.setAttribute("x", x); - glyph.setAttribute("y", "235"); - glyph.setAttribute("width", "25"); - glyph.setAttribute("height", "30"); - glyph.setAttribute("href", suit_href(suit)); - glyph.classList.add("suit-glyph"); - this.svg.append(glyph); + const glyph = create_svg_element(this.svg, "use", ["suit-glyph"], [["x", x], ["y", "235"], ["width", "25"], ["height", "30"], ["href", suit_href(suit)]]); x += 25; } - this.close_control = document.createElementNS(svgns, "rect"); - this.close_control.setAttribute("x", "460"); - this.close_control.setAttribute("y", "5"); - this.close_control.setAttribute("width", "35"); - this.close_control.setAttribute("height", "35"); - this.close_control.setAttribute("rx", "2"); - this.close_control.onclick = close; - this.close_control.classList.add("close-control"); - this.svg.append(this.close_control); - this.close_image = document.createElementNS(svgns, "image"); - this.close_image.setAttribute("x", "465"); - this.close_image.setAttribute("y", "10"); - this.close_image.setAttribute("width", "25"); - this.close_image.setAttribute("height", "25"); - this.close_image.setAttribute("href", "img/close.svg"); - this.close_image.classList.add("close-image"); - this.svg.append(this.close_image); - - this.chat_control = document.createElementNS(svgns, "rect"); - this.chat_control.setAttribute("x", "420"); - this.chat_control.setAttribute("y", "5"); - this.chat_control.setAttribute("width", "35"); - this.chat_control.setAttribute("height", "35"); - this.chat_control.setAttribute("rx", "2"); - this.chat_control.onclick = () => this.chatroom_container.classList.toggle("hidden"); - this.chat_control.classList.add("chat-control"); - this.svg.append(this.chat_control); - this.chat_image = document.createElementNS(svgns, "image"); - this.chat_image.setAttribute("x", "425"); - this.chat_image.setAttribute("y", "10"); - this.chat_image.setAttribute("width", "25"); - this.chat_image.setAttribute("height", "25"); - this.chat_image.setAttribute("href", "img/chat.svg"); - this.chat_image.classList.add("chat-image"); - this.svg.append(this.chat_image); - - this.container.append(this.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); - - 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}}); - } - } - - 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); - } - - player_angle(seat) { - const rel_seat = this.my_seat > seat ? this.num_seats + seat - this.my_seat : seat - this.my_seat; - if (this.num_seats % 4 === 0) { - if (rel_seat < this.num_seats / 4) { - return rel_seat * 2 * Math.PI / (this.num_seats + 2); - } else if (rel_seat >= this.num_seats / 4 && rel_seat <= 3 * this.num_seats / 4) { - return (rel_seat + 1) * 2 * Math.PI / (this.num_seats + 2); - } else { - return (rel_seat + 2) * 2 * Math.PI / (this.num_seats + 2); - } - } else { - return rel_seat * 2 * Math.PI / this.num_seats; - } + this.take_initial_actions(actions); } redraw_players() { @@ -283,52 +121,24 @@ export class KnockOutWhist { } card_image(username, card) { - const image = document.createElementNS(svgns, card === null ? "image" : "use"); - image.setAttribute("width", "45"); - image.setAttribute("height", "70"); - image.setAttribute("href", card_href(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: "PlayCard", card: card}}); } - this.svg.append(image); return image; } - 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) { - this.chatroom.take_action(user_action); + super.take_action(user_action); switch (user_action.action.action) { case "Message": break; case "Join": - this.seats.set(user_action.username, user_action.action.seat); this.hands.set(user_action.username, []); - this.num_seats = Math.max(...this.seats.values()) + 1; - this.my_seat = this.seats.get(this.username) || 0; this.redraw_players(); break; case "Leave": - this.seats.delete(user_action.username); this.hands.delete(user_action.username); - this.num_seats = Math.max(...this.seats.values()) + 1; - this.my_seat = this.seats.get(this.username) || 0; this.redraw_players(); break; case "KnockedOut": @@ -350,7 +160,7 @@ export class KnockOutWhist { this.hands.clear(); this.tricks_won.clear(); this.trumps = null; - this.active = this.player_after(user_action.username); + this.active = this.player_after(user_action.username, _ => true); this.redraw_trumps(); this.redraw_players(); break; @@ -387,7 +197,7 @@ export class KnockOutWhist { } }); this.hands.set(user_action.username, cards); - this.active = this.player_after(user_action.username); + this.active = this.player_after(user_action.username, _ => true); this.redraw_cards(); this.redraw_players(); break; @@ -423,7 +233,7 @@ export class KnockOutWhist { this.redraw_players(); break; case "EndDeal": - this.active = this.player_after(user_action.username); + this.active = this.player_after(user_action.username, _ => true); this.redraw_players(); break; case "WinGame":