Taipei Forcing Club

Computer science and contract bridge

Taiwan's first Ace on IntoBridge

I used to boycott IntoBridge because Taiwan was not in its country selector. A country dropdown looks trivial until your country is not in it. So I closed the tab and played bridge elsewhere.

Recently I gave it another try, and there it was: Taiwan, flag and all. So I stayed. Two weeks and 549 ranked games later, I became Taiwan’s first Ace, ranked #1 in Taiwan and #338 in the world.

My IntoBridge dashboard, showing rank #338, the Taiwanese flag, and a
month-long graph: flat at 7♦ until August 10, then a climb to Ace in two
weeks

Naturally I wanted to know when Taiwan was added. I found no announcement, so I asked Codex to investigate. I even disclosed my ulterior motive up front:

Humans are not completely rational and would waste tokens on showing off.

The investigation went considerably further than the brag required. Codex compared archived copies of the country list in the site’s JavaScript. The old list had 195 options; the current one has 197. The only additions are Taiwan and Hong Kong. Their flag files (tw.svg, hk.svg) carry exactly the same server timestamp, 25 February 2026 15:34:12 UTC, while most other flags date back to 2022. That is not an official release date, but it strongly suggests both were added together that day.

The Taiwan leaderboard on IntoBridge, with jdh8 at
#1

Was it rational to spend tokens on this? No. But the flag is there, my name sits on top of it, and now the date is on the record. Tokens well spent. 🇹🇼♣️

polish.club

I just moved Strawberry Polish Club to polish.club. One site for one bidding system feels cleaner than a site for multiple systems. My other bidding systems are also on their own sites:

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.