Taipei Forcing Club

Computer science and contract bridge

Strawberry adjunct

1m-(2M)-
3♠ = FG, 6+oM
3 = FG, 5=oM
3-3♠ = ask for stopper

I came up with this convention when I was writing system notes for my Polish Club.

Rebrand with a simple yet unique icon

A white club on a circle filled with red and blue check.  The colors symbolize my bidding systems, a Polish Club and a Blue Club.

IPv6 problem on Poker Chase

I encountered asset loading problem when playing Poker Chase in IPv6 networks. Asset loading fails from time to time. Sometimes I have to switch to mobile app to continue playing.

I found a solution on Firefox. Go to about:config and set network.http.fast-fallback-to-IPv4 to false. I am still not sure what causes the problem. I suspect it is related to the CDN used by the game.

Notifications when a Discord bot goes down

I want Natsuki to notify me when she goes down. This task is harder than I have thought. How can I count on a failing bot to send a message? I cannot trust the hosting provider, either. They may be the reason why the bot goes down. I have to rely on a third party.

Pair the bot with a web server

This sounds like a blasphemy against the single-responsibility principle. There are practical reasons to do this.

  1. Hosting providers may put idle bots to sleep. Some providers are so web-centric that they require serving a web page to keep the bot alive.
  2. HTTP(S) serves as a simple and reliable endpoint. Everyone online can ping it.

Below is how I pair Natsuki with an Axum server. The Axum server only serves 204 No Content at root.

#[shuttle_runtime::async_trait]
impl Service for Natsuki {
    async fn bind(mut self, addr: std::net::SocketAddr) -> Result<(), Error> {
        use axum::{response::NoContent, routing::get, Router};
        let router = Router::new().route("/", get(|| async { NoContent }));

        let (axum, serenity) = futures::join!(
            shuttle_axum::AxumService(router).bind(addr),
            self.0.start_autosharded(),
        );
        serenity.map_err(CustomError::new)?;
        axum
    }
}

Monitor the web server

The other part of the plan is a watchdog that periodically pings the web server. The watchdog informs me through a Discord channel whenever a ping fails. Since the web server is available worldwide, the watchdog can live anywhere such as GitHub Actions. Given the importance of GitHub, it is much more reliable than self-hosting.

My watchdog is open-source like Natsuki. It takes a Discord webhook to send messages. I make it an environment variable because the webhook contains sensitive data. If the webhook is leaked, other people can send arbitrary messages to the channel.

use dotenv::var;
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = reqwest::Client::new();
    let ping = || async {
        const ENDPOINT: &str = "https://natsuki-oehk.shuttle.app/";
        anyhow::ensure!(client.head(ENDPOINT).send().await?.status().is_success());
        Ok(())
    };
    if let Err(error) = ping().await {
        client
            .post(var("WEBHOOK")?)
            .json(&json!({ "content": error.to_string() }))
            .send()
            .await?;
    }
    Ok(())
}

Transfer O’ish Relay

An o’ish relay is a forcing bid that is natural or has extra strength. Famous examples are Birthright (Kokish Relay, 2♣-2-2) and Polish Club (1♣), after which I name this treatment. I advocate playing a transfer o’ish relay instead, the relay that either transfers to the next suit or has extra strength. This method renders the negative response nonforcing and allows more descriptive rebids.

Basic structure

  • TOR = transfer or extra
    • (R) = NF negative, accepts the transfer
  • TOR + 1 = transfer back to the suit of TOR

Examples

Birthright (Kokish Relay)

A classical problem with the strong 2♣ opening is whether the auction is game-forcing. One solution is the Kokish Relay, where opener bids 2 to show hearts or a unilaterally game-forcing hand. This treatment potentially wastes space because opener hardly passes 2-2♠.

The rebids after 2♣-2 I propose are:

2
5+ spades or FG
2♠
NF, 5+ hearts
2NT to 3
NAT NF

After 2, the default 2♠ fits any hand that responder would pass natural 2♠. Other responses are game-forcing.

After 2♠, responder passes with weak long spades. The 3 response is to play. Whether 2NT is game-forcing is up to partnership agreement.

Polish Club with swapped minor suits

1♣
Polish Club with diamonds instead of clubs:
  1. Balanced 12–14 HCP
  2. 11–17 HCP, 5+ diamonds or 4441
  3. 18+ HCP, any
1
11–17 HCP, 5+ clubs or (441)4

It took me almost one year to accept this idea until I realized that 1♣-1X-2♣ should retransfer to diamonds. The strength difference between natural diamonds (11+ HCP) and clubs (18+ HCP) is so large that opener should transfer twice. The odwrotka sequence of 1♣-1X-2 remains a power reverse.

Drawbacks

Responding to TOR + 1 is more complicated than TOR. You can no longer park at the negative relay (TOR + 1). Accepting the transfer (TOR + 5) becomes default and often meets a pass. Take 1 (showing clubs) for example:

  • Pass: weak long diamonds
  • 1NT: constructive natural, effectively 4+ diamonds
  • 2♣: almost a signoff with 3–4 clubs
  • 2♠: game try with 3+ clubs
  • 2NT: preemptive raise with 4+ clubs
  • 3♣: mixed raise with 4+ clubs

Renaming bidding systems

I’ve been with my girlfriend for 2+ months and finally found the red and blue mascots for my bridge bidding systems.

Oshawott is my girlfriend’s favorite pokémon. She thinks that Litten is my pokémon since my fursona is an orange cat. These two pokémons fit the color stickers from WBF systems policy and also the Red Oni, Blue Oni trope.

By the way, recently I reached 1800+ Elo on Cuebids. I took a screenshot because I not only was proud of it but also thought it cannot last long.

[Chen-Pang He reaching 1801 rated on Cuebids]

Rewriting Natsuki in Rust

I’m rewriting Natsuki (3.0) in Rust for several reasons. You can track my progress at this branch.

  1. Hosting: Free hosting tend to assume that node.js projects are websites. They put the app to sleep when idle, which effectively takes down a Discord bot. I’m now self-hosting Natsuki, but I’m not a fan of that. Rewriting in another language solves this issue.
  2. Economy commands: Shuttle provides free hosting even with 500 MB of database. This means we can make stateful features in the future, such as economy and games.
  3. Also please note that I’m considering remove NSFW functions. Discord has been more and more hostile to NSFW features. Making Natsuki SFW can spread her further.

Rebranding with a new icon

New icon

I made a new icon for the Taipei Forcing Club and this site. This icon honors both my hobbies: contract bridge and Japanese mahjong.

I used Inkscape to create the icon. I’m not a graphic designer, but I’m happy with the result. This icon combines lietxia’s 🀙 (1p) mahjong tile and ♣ in Noto Sans TC.

My bidding systems tested with Cuebids

I tested my bidding systems on Cuebids, a web app for pairs to practice bidding. All three accounts using my bidding systems made it to the top 10 this week. The following is the screenshot of the top 10 list when Mijumaru Blue Club (known as Bluepill (Canapé) Club then) just entered the list. My own account and Irene playing only Litten Polish Club were the top two.

We are still on top 5 as of this writing. We will try to stay on top 10 but not to be top 3 at the same time to keep the environment competitive.

My collection of bidding systems is available on GitHub. It contains aforementioned systems and a defensive system. Litten Polish Club is almost complete. Mijumaru Blue Club is still evolving. The defensive system is still in its infancy.

This collection is free software. You can use it for any purpose, including commercial purposes, at your own risk. I would appreciate it if you could share your experience with me.

NLTC, a good single hand evaluator

Inspired by Thomas Andrews’ article on single hand evaluators, I want to check out how good such an evaluator NLTC is. Since LTCs need an established trump fit, we only consider suit offense tricks, i.e. the most tricks a partnership can take in any suit contract.

Thomas built his data with Matt Ginsberg’s (GIB) double dummy library containing 700K+ deals. However, available sources of that library are long gone. To achieve similar statistical power, I would have to solve about 1M deals. Thanks to DDS, the well known double dummy solver in C++, along with modern computer architecture, we can solve 1M deals within one day.

I generated all data in this article with my bridge utility. It took 8 hours to solve 1M deals for only suit contracts. The following is the correlation coefficient matrix of various evaluations.

Tricks HCP+ BUM-RAP+ LTC NLTC ALTC
1 0.508270 0.512822 -0.482577 -0.521692 -0.516951
  1 0.987782 -0.861016 -0.927226 -0.903264
    1 -0.831184 -0.943001 -0.915663
      1 0.919761 0.935646
        1 0.979818
          1

The plus sign stands for short-suit points in GIB bid descriptions. As BUM-RAP gives fractional points, I made such an adjustment more rigorous.

  • S = (void = 3, singleton = 2, doubleton = 1)
  • X+ = max(X, S, X + S − 1) for each suit

As for how this adjustment is slightly better than max(X, S) and X + S, there will be a separate article.

ALTC is what Jeff Rubens suggested. Adjust −0.5 losers for each held ace and +0.5 for each guarded queen. NLTC bears this in mind but adjusts for missing aces and queens instead.

Things are different when we add up evaluations in each partnership. LTCs are less additive than HCP+. I think this phenomenon arises from counting values twice. A classic example is that a long suit in one hand and the corresponding doubleton in another are both counted as values a priori.

Tricks HCP+ BUM-RAP+ LTC NLTC ALTC
1 0.861012 0.870637 -0.749171 -0.839970 -0.813800
  1 0.987937 -0.832577 -0.910715 -0.880367
    1 -0.804589 -0.924311 -0.890025
      1 0.922495 0.940156
        1 0.974347
          1

Using NLTC in bidding

NLTC is more a single hand evaluator than an additive one. It is good to use NLTC for suit-oriented initial actions like preemptive openings and overcalls. Consider using additive point counts to assess supports.

The exact ranges of NLTC for preemptive openings are up to partnership agreement. I’d still like to point out some problems if you try to migrate from LTC to NLTC.

Don’t directly apply the rule of 2, 3, 4

With the plain LTC, we estimate the minimum playing tricks to be 13 − LTC. However, NLTC counts more losers than LTC, especially in single suiters. NLTC counts x and xx as 1.5 and 2.5 losers respectively, i.e. each 0.5 more than LTC. Single suiters are rich in singletons and doubletons. Besides, it is discouraged to preempt with a void. In general, NLTC intrinsically counts 1–1.5 more losers than LTC for single suiters. I am still not sure how to map NLTC to preemptive bids. There is going to be an update if I finally figure it out.