A high-performance lookup-table algorithm for the Indian card game Teen Patti, with full support for Joker (wild card). Given any 3-card hand, the library instantly returns the hand's rank, type, and best possible combination. Available for Java, Go and C++, with identical behavior.
Derived from texas_algorithm.
teenpatti_algorithm/
├── java/ # Java implementation (published to Maven Central)
├── go/ # Go implementation (go:embed based, byte-identical lookup table)
├── cpp/ # C++17 implementation (CMake, data embedded via .incbin)
└── .github/ # CI: Java CI (maven.yml), Go CI (go.yml), C++ CI (cpp.yml), publishing (publish.yml)
All three implementations share the same teenpatti_data.txt lookup table and pass the same set of unit tests; the Go and C++ generation pipelines reproduce the table byte-for-byte.
- Project Structure
- Game Rules Overview
- Quick Start
- API Reference
- Card Notation
- Hand Rankings
- Algorithm Design
- Generating the Lookup Table
- Running the Demo
- Related Projects
Teen Patti is played with a standard 52-card deck plus 3 Jokers (55 cards total). Each player is dealt 3 cards. The goal is to have the best 3-card hand. Jokers are wild — they substitute for any card to form the best possible combination.
<dependency>
<groupId>com.github.esrrhs</groupId>
<artifactId>teenpatti_algorithm</artifactId>
<version>1.0.3</version>
</dependency>// 1. Load the lookup table once at startup
TeenPattiAlgorithmUtil.load();
// 2. Get the hand type (returns an integer constant, see Hand Rankings)
int type = TeenPattiAlgorithmUtil.getWinType("黑A,方A,鬼");
// type == 6 → Three of a Kind (Joker acts as a third Ace)
// 3. Get the rank position (higher = stronger hand)
int position = TeenPattiAlgorithmUtil.getWinPosition("黑2,黑3,黑4");
// 4. Compare two hands (positive = first hand wins, negative = second wins, 0 = tie)
int result = TeenPattiAlgorithmUtil.compare("黑A,方A,鬼", "黑A,鬼,方3");
// 5. Get the best resolved hand (Joker expanded to its optimal card)
int maxKey = TeenPattiAlgorithmUtil.getMax("黑A,方A,鬼");
String maxStr = TeenPattiAlgorithmUtil.keyToStr(maxKey); // e.g. "方A黑A梅A" (three Aces)go get github.com/esrrhs/teenpatti_algorithm/gopackage main
import (
"fmt"
teenpatti "github.com/esrrhs/teenpatti_algorithm/go"
)
func main() {
// 1. Load the embedded lookup table once at startup
if err := teenpatti.Load(); err != nil {
panic(err)
}
// 2. Get the hand type (returns a CardType constant, see Hand Rankings)
typ := teenpatti.GetWinType("黑A,方A,鬼")
// typ == 6 → Three of a Kind (Joker acts as a third Ace)
// 3. Get the rank position (higher = stronger hand)
position := teenpatti.GetWinPosition("黑2,黑3,黑4")
// 4. Compare two hands (positive = first hand wins, negative = second wins, 0 = tie)
result := teenpatti.Compare("黑A,方A,鬼", "黑A,鬼,方3")
// 5. Get the best resolved hand (Joker expanded to its optimal card)
maxKey := teenpatti.GetMax("黑A,方A,鬼")
maxStr := teenpatti.KeyToStr(maxKey)
fmt.Println(typ, position, result, maxKey, maxStr)
}cmake -S cpp -B cpp/build -DCMAKE_BUILD_TYPE=Release
cmake --build cpp/build#include <teenpatti/teenpatti.hpp>
// 1. Load the embedded lookup table once at startup
if (!teenpatti::load()) {
return 1;
}
// 2. Get the hand type (returns a CardType constant, see Hand Rankings)
int type = teenpatti::get_win_type("黑A,方A,鬼");
// type == 6 → Three of a Kind (Joker acts as a third Ace)
// 3. Get the rank position (higher = stronger hand)
int position = teenpatti::get_win_position("黑2,黑3,黑4");
// 4. Compare two hands (positive = first hand wins, negative = second wins, 0 = tie)
int result = teenpatti::compare("黑A,方A,鬼", "黑A,鬼,方3");
// 5. Get the best resolved hand (Joker expanded to its optimal card)
int maxKey = teenpatti::get_max("黑A,方A,鬼");
std::string maxStr = teenpatti::key_to_str(maxKey); // e.g. "方A黑A梅A" (three Aces)All public methods are on TeenPattiAlgorithmUtil.
| Method | Parameters | Return | Description |
|---|---|---|---|
load() |
— | void |
Load lookup table from teenpatti_data.txt (call once at startup) |
loadNormal(InputStream) |
input stream | void |
Load lookup table from a custom stream |
getWinType(String) |
comma-separated cards | int |
Hand type constant (see Hand Rankings) |
getWinPosition(String) |
comma-separated cards | int |
Global rank (higher = stronger) |
getMax(String) |
comma-separated cards | int |
Encoded key of the best resolved hand |
compare(String, String) |
two hands | int |
Positive/zero/negative comparison result |
keyToStr(int) |
encoded key | String |
Human-readable card string |
getKeyData() returns a KeyData object with three fields:
| Field | Getter | Description |
|---|---|---|
position |
getPosition() / getPostion() |
Global rank index among all possible hands |
type |
getType() |
Hand type (1–6, see Hand Rankings) |
max |
getMax() |
Encoded key of best resolved hand |
All functions live in package teenpatti (github.com/esrrhs/teenpatti_algorithm/go).
| Function | Parameters | Return | Description |
|---|---|---|---|
Load() |
— | error |
Load the embedded teenpatti_data.txt (call once at startup) |
LoadFromFile(path) |
file path | error |
Load the lookup table from a custom file |
GetWinType(string) |
comma-separated cards | int |
Hand type constant (see Hand Rankings) |
GetWinPosition(string) |
comma-separated cards | int |
Global rank (higher = stronger) |
GetMax(string) |
comma-separated cards | int |
Encoded key of the best resolved hand |
Compare(a, b string) |
two hands | int |
Positive/zero/negative comparison result |
KeyToStr(int) |
encoded key | string |
Human-readable card string |
The ...ByCards variants (GetWinTypeByCards, GetWinPositionByCards, GetMaxByCards, CompareByCards) accept parsed []byte card slices instead of strings; GetKeyDataByCards / GetKeyDataByKey return a *KeyData with Position, Type and Max fields. CompareCards / MaxCards / GetCardTypeUnordered operate on []Poke directly, mirroring TeenPattiCardUtil.
All functions live in namespace teenpatti (header <teenpatti/teenpatti.hpp>, library target teenpatti).
| Function | Return | Description |
|---|---|---|
load() |
bool |
Load the embedded teenpatti_data.txt (call once at startup) |
load_from_file(path) / load_from_stream(in) |
bool |
Load the lookup table from a custom file/stream |
get_win_type(cards) |
int |
Hand type constant (see Hand Rankings) |
get_win_position(cards) |
int |
Global rank (higher = stronger) |
get_max(cards) |
int |
Encoded key of the best resolved hand |
compare(a, b) |
int |
Positive/zero/negative comparison result |
key_to_str(key) |
std::string |
Human-readable card string |
The cards parameter is overloaded: std::string ("黑A,方A,鬼"), std::vector<std::uint8_t> (packed card bytes) or int (encoded key). get_key_data(cards) returns a std::optional<KeyData> with position, type and max; compare_cards / max_cards / get_card_type_unordered operate on std::vector<Poke> directly, mirroring TeenPattiCardUtil.
Cards are written as <suit><value> in Chinese notation, separated by commas.
| Notation | Suit | English |
|---|---|---|
方 |
♦ | Diamonds |
梅 |
♣ | Clubs |
红 |
♥ | Hearts |
黑 |
♠ | Spades |
2 3 4 5 6 7 8 9 10 J Q K A
Write 鬼 for a wild Joker card. There are 3 Jokers in the deck.
| Input string | Meaning |
|---|---|
"黑A,方A,鬼" |
Ace of Spades, Ace of Diamonds, Joker |
"黑2,黑3,黑4" |
2♠ 3♠ 4♠ |
"红K,梅Q,方J" |
K♥ Q♣ J♦ |
Ranked from lowest to highest:
| Rank | Type constant | Name | Description |
|---|---|---|---|
| 1 | TEENPATTI_CARD_TYPE_GAOPAI = 1 |
High Card | No combination; highest card wins |
| 2 | TEENPATTI_CARD_TYPE_DUIZI = 2 |
Pair | Two cards of the same value |
| 3 | TEENPATTI_CARD_TYPE_TONGHUA = 3 |
Flush | All three cards of the same suit |
| 4 | TEENPATTI_CARD_TYPE_SHUNZI = 4 |
Straight | Three consecutive values (A-2-3 also valid) |
| 5 | TEENPATTI_CARD_TYPE_TONGHUASHUN = 5 |
Straight Flush | Consecutive values, all same suit |
| 6 | TEENPATTI_CARD_TYPE_SANTIAO = 6 |
Three of a Kind | All three cards of the same value |
Note: Three of a Kind ranks higher than Straight Flush in Teen Patti, unlike Texas Hold'em.
Tie-breaking rules:
- Three of a Kind / Pair: compare by the repeated card's value, then the kicker.
- All others: compare highest card first, then second, then third.
The library uses a pre-computed lookup table. At query time, a 3-card hand is encoded into a single integer key; the key is looked up in a ConcurrentHashMap to retrieve the hand's rank, type, and best expansion in O(1).
Each card is packed into one byte: the upper 4 bits hold the suit (0–3), the lower 4 bits hold the value (2–14). A Joker uses a reserved (color=5, value=8) sentinel.
byte = (suit << 4) | value
A 3-card hand is encoded into a single int by concatenating the three byte values in decimal:
key = card1_byte * 10000 + card2_byte * 100 + card3_byte
Cards are sorted before encoding so the same set always produces the same key regardless of order.
The deck contains 55 cards (52 regular + 3 Jokers). All C(55, 3) = 26,235 unique combinations are enumerated using a recursive combination generator. Duplicate-key hands (e.g. a hand where Joker resolves to an identical state) are deduplicated.
All combination keys are sorted by hand strength using a parallel quicksort (Sorter.java / sorter.go / teenpatti.cpp). The thread pool size equals the number of available CPU cores. When the number of active threads exceeds 2 × CPU_CORES, sub-partitions fall back to in-thread recursion to avoid thread explosion.
The comparison function (GenUtil.compare) resolves Jokers to their best possible substitution before comparing, so the sort order reflects the true game outcome.
After sorting, each entry is written to teenpatti_data.txt with:
<key> <rank> <rank_index> <total> <best_hand_str> <hand_type> <best_hand_key> <best_hand_readable>
The rank index is incremented only when two adjacent hands are not equal in strength, producing a dense sequential ranking.
input string → parse cards → sort bytes → encode key → HashMap.get(key) → KeyData{position, type, max}
Run TeenPattiAlgorithmUtil.main() (or GenUtil.genKey() + GenUtil.outputData()) to regenerate teenpatti_data.txt. This is only needed if you modify the deck or ranking rules.
# Java
cd java && mvn exec:java -Dexec.mainClass="com.github.esrrhs.teenpatti_algorithm.TeenPattiAlgorithmUtil"
# Go (writes teenpatti_data.txt into the working directory)
cd go && go run ./cmd/teenpatti_gen
# C++ (writes teenpatti_data.txt into the working directory)
cmake --build cpp/build --target teenpatti_gen && cpp/build/teenpatti_genThe generation process prints progress with estimated time remaining and throughput (entries/sec). The Go and C++ implementations reproduce the Java-generated table byte-for-byte.
TestUtil.main() loads the table and prints results for two sample hands:
TeenPattiAlgorithmUtil.load();
String cards = "黑A,方A,鬼"; // A♠ A♦ Joker → Three Aces
String cards1 = "黑A,鬼,方3"; // A♠ Joker 3♦ → Pair of Aces
System.out.println(TeenPattiAlgorithmUtil.getWinPosition(cards)); // rank
System.out.println(TeenPattiAlgorithmUtil.getWinType(cards)); // 6 = Three of a Kind
System.out.println(TeenPattiAlgorithmUtil.keyToStr(
TeenPattiAlgorithmUtil.getMax(cards))); // best resolved hand
System.out.println(TeenPattiAlgorithmUtil.compare(cards, cards1)); // > 0: cards winsThe same demo in Go:
teenpatti.Load()
cards := "黑A,方A,鬼" // A♠ A♦ Joker → Three Aces
cards1 := "黑A,鬼,方3" // A♠ Joker 3♦ → Pair of Aces
fmt.Println(teenpatti.GetWinPosition(cards)) // rank
fmt.Println(teenpatti.GetWinType(cards)) // 6 = Three of a Kind
fmt.Println(teenpatti.KeyToStr(teenpatti.GetMax(cards))) // best resolved hand
fmt.Println(teenpatti.Compare(cards, cards1)) // > 0: cards winsAnd in C++:
teenpatti::load();
std::string cards = "黑A,方A,鬼"; // A♠ A♦ Joker → Three Aces
std::string cards1 = "黑A,鬼,方3"; // A♠ Joker 3♦ → Pair of Aces
std::cout << teenpatti::get_win_position(cards) << "\n"; // rank
std::cout << teenpatti::get_win_type(cards) << "\n"; // 6 = Three of a Kind
std::cout << teenpatti::key_to_str(teenpatti::get_max(cards)) << "\n"; // best resolved hand
std::cout << teenpatti::compare(cards, cards1) << "\n"; // > 0: cards wins- majiang_algorithm — Mahjong algorithm
- texas_algorithm — Texas Hold'em algorithm
This project is licensed under the MIT License.