Tournament director tools for Go tournaments
Project description
lib
Tournament director tools for US Go tournaments. A Python library for managing Go tournament pairings, handicaps, and standings using Swiss and McMahon systems.
Features
- Go Rank System - Full support for kyu/dan ranks (30k to 9d) with arithmetic operations
- Handicap Calculation - AGA-compliant handicap and komi calculation
- Swiss Pairing - Score-based pairing with repeat prevention and color balancing
- McMahon Pairing - Rank-based initial scores with configurable bar
- Standings Calculator - Configurable tiebreakers (SOS, SODOS, Extended SOS)
- Cross-Division Play - Players paired across divisions with division-specific prizes
Installation
uv add nyig-td
Or with pip:
pip install nyig-td
Quick Start
Basic Swiss Tournament
from nyig_td import (
Tournament, TournamentSettings, Player,
PairingAlgorithm, GameResult, RoundStatus,
SwissPairingEngine, StandingsCalculator
)
# Create a 4-round Swiss tournament
settings = TournamentSettings(
num_rounds=4,
pairing_algorithm=PairingAlgorithm.SWISS,
handicap_enabled=True
)
tournament = Tournament.create("Club Championship", settings)
# Register players
tournament.add_player(Player.create("Alice", "3d", club="NYC Go Club"))
tournament.add_player(Player.create("Bob", "2d", club="Brooklyn Go"))
tournament.add_player(Player.create("Carol", "1k", club="Queens Go"))
tournament.add_player(Player.create("Dave", "5k", club="NYC Go Club"))
# Generate round 1 pairings
engine = SwissPairingEngine()
result = engine.generate_pairings(tournament, round_number=1)
# Print pairings
for pairing in result.pairings:
black = tournament.players[pairing.black_player_id]
white = tournament.players[pairing.white_player_id]
print(f"Board {pairing.board_number}: {black.name} (B) vs {white.name} (W)")
if pairing.handicap_stones > 0:
print(f" Handicap: {pairing.handicap_stones} stones")
# Record results
round1 = tournament.get_round(1)
round1.pairings = result.pairings
round1.pairings[0].result = GameResult.WHITE_WIN
round1.pairings[1].result = GameResult.BLACK_WIN
round1.status = RoundStatus.COMPLETED
# Calculate standings
calc = StandingsCalculator()
standings = calc.calculate(tournament)
for s in standings:
print(f"{s.rank}. {s.player.name} - {s.wins}-{s.losses}")
McMahon Tournament
McMahon pairing assigns initial scores based on rank, allowing players of different strengths to compete fairly:
from nyig_td import (
Tournament, TournamentSettings, Player,
PairingAlgorithm, McMahonPairingEngine
)
settings = TournamentSettings(
num_rounds=5,
pairing_algorithm=PairingAlgorithm.MCMAHON,
mcmahon_bar="3d", # Players at 3d+ start at score 0
handicap_enabled=True
)
tournament = Tournament.create("US Go Congress Open", settings)
# Add players - they'll get initial McMahon scores based on rank
tournament.add_player(Player.create("Strong Dan", "5d")) # Starts at 0
tournament.add_player(Player.create("At Bar", "3d")) # Starts at 0
tournament.add_player(Player.create("Below Bar", "1k")) # Starts at -3
engine = McMahonPairingEngine(bar_rank="3d")
# Check initial McMahon scores
for player in tournament.players.values():
score = engine.get_initial_mcmahon_score(player)
print(f"{player.name} ({player.rank}): MM score = {score}")
Core Concepts
Ranks
Go ranks are represented with the Rank class:
from nyig_td import Rank, RankType, validate_rank
# Create ranks
r1 = Rank.from_string("5k") # 5 kyu
r2 = Rank.from_dan(3) # 3 dan
r3 = Rank.from_kyu(10) # 10 kyu
# Rank properties
print(r1.rank_type) # RankType.KYU
print(r2.level) # 3
# Rank comparison (higher rank = stronger)
print(r2 > r1) # True (3d > 5k)
# Rank difference
print(r2.difference(r1)) # 7 (3d is 7 stones stronger than 5k)
print(r1.stones_difference(r2)) # 7 (absolute difference)
# Validation
print(validate_rank("5k")) # True
print(validate_rank("15d")) # False
Handicap Calculation
Calculate appropriate handicap for games between players of different ranks:
from nyig_td import Rank, HandicapCalculator
calc = HandicapCalculator()
# Standard calculation
white = Rank.from_string("3d") # Stronger player
black = Rank.from_string("5k") # Weaker player
handicap = calc.calculate(white, black)
print(handicap.stones) # 7 (capped at 9)
print(handicap.komi) # 0.5
# Convenience method with strings
handicap = calc.calculate_from_strings("3d", "1k")
# Custom settings
calc = HandicapCalculator(
even_komi=7.5, # Komi for even games
handicap_komi=0.5, # Komi for handicap games
max_stones=9, # Maximum handicap stones
reduction=1 # Reduce calculated handicap by 1
)
Players
Players have ranks, optional club affiliations, and AGA IDs:
from nyig_td import Player, Rank
# Create with string rank
player = Player.create(
name="Alice Chen",
rank="3d",
club="NYC Go Club",
aga_id="12345",
rating=2150.5
)
# Create with Rank object
rank = Rank.from_dan(3)
player = Player.create("Alice Chen", rank)
# Partial round participation (for players who can't play all rounds)
player.rounds_participating = {1, 2, 4} # Only plays rounds 1, 2, and 4
Tournament Configuration
from nyig_td import TournamentSettings, PairingAlgorithm, StandingsWeights
settings = TournamentSettings(
num_rounds=5, # 1-10 rounds
pairing_algorithm=PairingAlgorithm.MCMAHON,
handicap_enabled=True,
handicap_reduction=0, # Reduce all handicaps by N
mcmahon_bar="3d", # McMahon bar rank
standings_weights=StandingsWeights(
wins=1.0,
sos=0.1, # Sum of Opponents' Scores
sodos=0.05, # Sum of Defeated Opponents' Scores
extended_sos=0.01
)
)
Pairing Results
The pairing engine returns pairings, byes, and warnings:
result = engine.generate_pairings(tournament, round_number=1)
# Pairings
for pairing in result.pairings:
print(f"Board {pairing.board_number}")
print(f" Black: {pairing.black_player_id}")
print(f" White: {pairing.white_player_id}")
print(f" Handicap: {pairing.handicap_stones} stones, {pairing.komi} komi")
# Byes (odd number of players)
for bye in result.byes:
print(f"Bye: {bye.player_id} ({bye.points} points)")
# Warnings (repeat pairings, etc.)
for warning in result.warnings:
print(f"Warning: {warning}")
Recording Results
from nyig_td import GameResult, RoundStatus
round1 = tournament.get_round(1)
# Set results
round1.pairings[0].result = GameResult.BLACK_WIN
round1.pairings[1].result = GameResult.WHITE_WIN
round1.pairings[2].result = GameResult.DRAW # Rare in Go
round1.pairings[3].result = GameResult.BLACK_WIN_FORFEIT
round1.pairings[4].result = GameResult.BOTH_LOSE # Double forfeit
# Mark round complete
round1.status = RoundStatus.COMPLETED
Standings
Calculate standings with configurable tiebreakers:
from nyig_td import StandingsCalculator, StandingsWeights
# Default weights
calc = StandingsCalculator()
# Custom weights
weights = StandingsWeights(
wins=1.0,
sos=0.2, # Higher SOS weight
sodos=0.1,
extended_sos=0.05
)
calc = StandingsCalculator(weights=weights)
# Calculate through specific round
standings = calc.calculate(tournament, through_round=3)
# Or auto-detect last completed round
standings = calc.calculate(tournament)
# Print standings
for s in standings:
print(f"{s.rank}. {s.player.name} ({s.player.rank})")
print(f" Record: {s.wins}-{s.losses}")
print(f" SOS: {s.sos:.1f}, SODOS: {s.sodos:.1f}")
print(f" Total Score: {s.total_score:.3f}")
Advanced Usage
Cross-Division McMahon Tournament
In McMahon tournaments, players are paired across divisions based on score, but prizes are awarded within divisions:
from nyig_td import Rank
from nyig_td.standings import PlayerStanding
# Define divisions for prizes
class Division:
def __init__(self, name: str, min_rank: Rank, max_rank: Rank):
self.name = name
self.min_rank = min_rank
self.max_rank = max_rank
def contains(self, rank: Rank) -> bool:
return self.min_rank.value <= rank.value <= self.max_rank.value
def filter_standings(self, standings: list) -> list:
return [s for s in standings if self.contains(s.player.rank)]
divisions = [
Division("Dan", Rank.from_dan(1), Rank.from_dan(9)),
Division("SDK", Rank.from_kyu(9), Rank.from_kyu(1)),
Division("DDK", Rank.from_kyu(30), Rank.from_kyu(10)),
]
# Get overall standings (pairing is cross-division)
overall = calc.calculate(tournament)
# Filter for each division's prizes
for div in divisions:
div_standings = div.filter_standings(overall)
print(f"\n{div.name} Division Winner: {div_standings[0].player.name}")
Custom McMahon Initial Scores
Override automatic McMahon score calculation for specific players:
player = Player.create("Special Case", "5k")
player.initial_mcmahon_score = 0 # Place at bar regardless of rank
tournament.add_player(player)
Factory Function for Pairing Engines
from nyig_td import get_pairing_engine, PairingAlgorithm
# Get appropriate engine based on settings
engine = get_pairing_engine(
algorithm=PairingAlgorithm.MCMAHON,
bar_rank="2d"
)
Examples
See the examples/ directory for complete working examples:
swiss_tournament.py- 4-round Swiss tournament with 8 playersmcmahon_tournament.py- 5-round McMahon tournament with 16 playersmcmahon_divisions.py- Cross-division McMahon with division prizes
Run an example:
python examples/swiss_tournament.py
API Reference
Main Classes
| Class | Description |
|---|---|
Rank |
Go rank representation (kyu/dan) |
Player |
Tournament participant |
Tournament |
Full tournament state |
TournamentSettings |
Tournament configuration |
Pairing |
A game pairing between two players |
Round |
A tournament round with pairings and byes |
Pairing Engines
| Class | Description |
|---|---|
SwissPairingEngine |
Swiss system pairing |
McMahonPairingEngine |
McMahon system pairing |
get_pairing_engine() |
Factory function |
Calculators
| Class | Description |
|---|---|
HandicapCalculator |
Calculate handicap stones and komi |
StandingsCalculator |
Calculate tournament standings |
Enums
| Enum | Values |
|---|---|
RankType |
KYU, DAN |
GameResult |
BLACK_WIN, WHITE_WIN, DRAW, NO_RESULT, BLACK_WIN_FORFEIT, WHITE_WIN_FORFEIT, BOTH_LOSE |
RoundStatus |
PENDING, PAIRED, IN_PROGRESS, COMPLETED |
PairingAlgorithm |
SWISS, MCMAHON |
TournamentStatus |
SETUP, REGISTRATION, IN_PROGRESS, COMPLETED |
Requirements
- Python 3.11+
License
MIT License - see LICENSE file for details.
Contributing
See DEVELOPMENT.md for development setup and contribution guidelines.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nyig_td-0.1.2.tar.gz.
File metadata
- Download URL: nyig_td-0.1.2.tar.gz
- Upload date:
- Size: 48.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95ed81170e6559a45dca6dd024ba5ebec6dee364cba567745da9ad8bbe32aa7d
|
|
| MD5 |
94d6373b614f9ef108cd8d275302c220
|
|
| BLAKE2b-256 |
84867cabde945afaa20c20ac20401976636b1effc26b6f2410f55826fa095c91
|
Provenance
The following attestation bundles were made for nyig_td-0.1.2.tar.gz:
Publisher:
publish.yml on liryan1/nyig-td
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nyig_td-0.1.2.tar.gz -
Subject digest:
95ed81170e6559a45dca6dd024ba5ebec6dee364cba567745da9ad8bbe32aa7d - Sigstore transparency entry: 1029281483
- Sigstore integration time:
-
Permalink:
liryan1/nyig-td@c8c7aa582469234a1887a47dbb28bf068134c0d0 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/liryan1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c8c7aa582469234a1887a47dbb28bf068134c0d0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nyig_td-0.1.2-py3-none-any.whl.
File metadata
- Download URL: nyig_td-0.1.2-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17e0351d1d13d8a4edac81df44e3cd787bcc024fb49f7a61023ebe525a178f1a
|
|
| MD5 |
4355cb4366a65bdfe4a8ac6abf6dcf72
|
|
| BLAKE2b-256 |
8603483632583cd34725a33a15025b62a4b8b2f293b5f2cf395da0f045b0c00f
|
Provenance
The following attestation bundles were made for nyig_td-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on liryan1/nyig-td
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nyig_td-0.1.2-py3-none-any.whl -
Subject digest:
17e0351d1d13d8a4edac81df44e3cd787bcc024fb49f7a61023ebe525a178f1a - Sigstore transparency entry: 1029281508
- Sigstore integration time:
-
Permalink:
liryan1/nyig-td@c8c7aa582469234a1887a47dbb28bf068134c0d0 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/liryan1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c8c7aa582469234a1887a47dbb28bf068134c0d0 -
Trigger Event:
push
-
Statement type: