diff --git a/.gitignore b/.gitignore index 40238cc..5771b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.claude target /docker/data /.idea diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ed31748 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,160 @@ +# Pairgoth Project + +## Purpose + +**Pairgoth** is a modern Go tournament pairing engine - successor to OpenGotha. It manages tournaments using Swiss and MacMahon pairing systems, handling player registration, automatic pairing, results entry, and standings calculation. + +**Version:** 0.20 | **License:** org.jeudego (French Go Association) + +## Tech Stack + +- **Backend:** Kotlin 2.1 + Maven + Jetty 10 +- **Frontend:** Fomantic UI CSS 2.9.2 + Vanilla JavaScript (no jQuery/React) +- **Templates:** Apache Velocity 2.4 +- **Storage:** File-based XML (no database required) +- **JDK:** 11+ + +## Project Structure + +``` +pairgoth/ +├── pairgoth-common/ # Shared utilities (JSON, XML, crypto, logging) +├── api-webapp/ # REST API backend (port 8085) +│ └── model/ # Domain: Tournament, Player, Game, Pairing +│ └── pairing/ # Solvers: Swiss, MacMahon algorithms +│ └── store/ # Persistence: File/Memory storage +│ └── api/ # Handlers: Tournament, Player, Results, etc. +│ └── ext/ # OpenGotha import/export +├── view-webapp/ # Web UI frontend (port 8080) +│ └── webapp/js/ # Vanilla JS: domhelper, api, main, tour-*.inc +│ └── webapp/sass/ # Styles: main, tour, explain, index +│ └── templates/ # Velocity: index, tour, explain, login +│ └── kotlin/ # Servlets, OAuth, Ratings integration +├── webserver/ # Standalone Jetty launcher +├── application/ # Final JAR packaging +└── docker/ # Container deployment +``` + +## Architecture + +### Dual-Webapp Pattern + +``` +[Browser] <--8080--> [view-webapp] <--8085--> [api-webapp] + │ │ │ + Velocity HTML ApiClient.kt REST JSON + + vanilla JS + FileStore +``` + +- **api-webapp** - Pure REST API, business logic, pairing engine +- **view-webapp** - Web UI, proxies API calls, handles auth/i18n/ratings + +### Key Architectural Decisions + +1. **No JS Framework** - 2200 lines of vanilla JS vs typical 50KB+ bundle +2. **Fomantic CSS Only** - Using CSS framework without its jQuery-dependent JS +3. **CSS @layer** - Clean cascade: `semantic` layer < `pairgoth` layer +4. **File Storage** - XML files for portability, no database setup needed +5. **Read/Write Locks** - Simple concurrency on API servlet +6. **SSE Events** - Real-time updates via Server-Sent Events + +## Domain Model + +``` +Tournament (sealed class) +├── IndividualTournament +├── PairTournament +├── TeamTournament +└── RengoTournament + +Player → Pairable (interface) +Game { white, black, result, handicap } +Pairing { Swiss | MacMahon } +TimeSystem { ByoYomi | SuddenDeath | Canadian | Fischer } +Rules { French | Japanese | AGA | Chinese } +``` + +## Pairing Engine + +Location: `api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/` + +- **SwissSolver** - Swiss system pairing algorithm +- **MacMahonSolver** - MacMahon bands system +- **HistoryHelper** - Criteria: wins, SOS, SOSOS, colors, CUSS, etc. +- **PairingListener** - Progress callbacks for UI + +## Key Files + +| Purpose | Path | +|---------|------| +| DOM utilities | `view-webapp/.../js/domhelper.js` | +| API client | `view-webapp/.../js/api.js` | +| Core UI | `view-webapp/.../js/main.js` | +| Main styles | `view-webapp/.../sass/main.scss` | +| Tournament model | `api-webapp/.../model/Tournament.kt` | +| Swiss solver | `api-webapp/.../pairing/solver/SwissSolver.kt` | +| API router | `api-webapp/.../server/ApiServlet.kt` | +| App launcher | `webserver/.../application/Pairgoth.kt` | + +## Build & Run + +```bash +# Build +mvn clean package + +# Run standalone (both webapps) +java -jar application/target/pairgoth-engine.jar + +# Or separate: +# API: localhost:8085/api +# UI: localhost:8080/ +``` + +## Configuration + +File: `pairgoth.properties` (user) or `pairgoth.default.properties` (defaults) + +```properties +webapp.port = 8080 +api.port = 8085 +store = file # file | memory +store.file.path = tournamentfiles +auth = none # none | oauth | sesame +``` + +## Frontend Patterns + +### State via CSS Classes +- `.active` - tabs, accordions, visible elements +- `.shown` - modals/popups +- `.hidden` / `.disabled` / `.selected` / `.dimmed` + +### Component Communication +```javascript +// Custom events +box.dispatchEvent(new CustomEvent('listitem-dblclk', { detail: id })); + +// jQuery-like API (domhelper.js) +$('.item').addClass('active').on('click', handler); +``` + +### API Integration +```javascript +api.getJson('/tour/123/players') + .then(players => render(players)) + .catch(err => error(err)); +``` + +## External Integrations + +- **Ratings:** FFG (French), EGF (European), AGA (Australian) +- **OAuth:** FFG, Google, Facebook, Twitter, Instagram +- **Import/Export:** OpenGotha XML format compatibility + +## i18n + +Translations in `view-webapp/.../WEB-INF/translations/` +- English (default) +- French (fr) +- German (de) +- Korean (ko) diff --git a/doc/API.md b/doc/API.md index d40645e..29e5bb0 100644 --- a/doc/API.md +++ b/doc/API.md @@ -2,158 +2,285 @@ ## General remarks -The API expects an `Accept` header of `application/json`, with no encoding or an `UTF-8` encoding. Exceptions are some export operations which can have different MIME types to specify the expected format. +The API expects an `Accept` header of `application/json`, with no encoding or an `UTF-8` encoding. Exceptions are some export operations which can have different MIME types to specify the expected format: +- `application/json` - JSON output (default) +- `application/xml` - OpenGotha XML export +- `application/egf` - EGF format +- `application/ffg` - FFG format +- `text/csv` - CSV format GET requests return either an array or an object, as specified below. -POST, PUT and DELETE requests return either the 200 HTTP code with `{ "success": true }` (with an optional `"id"` field for some POST requests), or and invalid HTTP code and (for some errors) the body `{ "success": false, "error": }`. +POST, PUT and DELETE requests return either the 200 HTTP code with `{ "success": true }` (with an optional `"id"` field for some POST requests), or an invalid HTTP code and (for some errors) the body `{ "success": false, "error": }`. + +All POST/PUT/DELETE requests use read/write locks for concurrency. GET requests use read locks. + +When authentication is enabled, all requests require an `Authorization` header. ## Synopsis -+ /api/tour GET POST Tournaments handling -+ /api/tour/#tid GET PUT DELETE Tournaments handling -+ /api/tour/#tid/part GET POST Registration handling -+ /api/tour/#tid/part/#pid GET PUT DELETE Registration handling -+ /api/tour/#tid/team GET POST Team handling -+ /api/tour/#tid/team/#tid GET PUT DELETE Team handling -+ /api/tour/#tid/pair/#rn GET POST PUT DELETE Pairing -+ /api/tour/#tid/res/#rn GET PUT DELETE Results -+ /api/tour/#tid/standings GET Standings -+ /api/tour/#tid/stand/#rn GET Standings ++ /api/tour GET POST Tournaments handling ++ /api/tour/#tid GET PUT DELETE Tournaments handling ++ /api/tour/#tid/part GET POST Registration handling ++ /api/tour/#tid/part/#pid GET PUT DELETE Registration handling ++ /api/tour/#tid/team GET POST Team handling ++ /api/tour/#tid/team/#tid GET PUT DELETE Team handling ++ /api/tour/#tid/pair/#rn GET POST PUT DELETE Pairing ++ /api/tour/#tid/res/#rn GET PUT DELETE Results ++ /api/tour/#tid/standings GET PUT Standings ++ /api/tour/#tid/stand/#rn GET Standings ++ /api/tour/#tid/explain/#rn GET Pairing explanation ++ /api/token GET POST DELETE Authentication ## Tournament handling + `GET /api/tour` Get a list of known tournaments ids - - *output* json map (id towards shortName) of known tournaments (subject to change) + + *output* json map (id towards shortName) of known tournaments + `GET /api/tour/#tid` Get the details of tournament #tid - + *output* json object for tournament #tid + Supports `Accept: application/xml` to get OpenGotha XML export. + + `POST /api/tour` Create a new tournament - - *input* json object for new tournament (see `Tournament.fromJson` in the sources) - + + *input* json object for new tournament, or OpenGotha XML with `Content-Type: application/xml` + + Tournament JSON structure: + ```json + { + "type": "INDIVIDUAL", + "name": "Tournament Name", + "shortName": "TN", + "startDate": "2024-01-15", + "endDate": "2024-01-16", + "country": "fr", + "location": "Paris", + "online": false, + "rounds": 5, + "gobanSize": 19, + "rules": "FRENCH", + "komi": 7.5, + "timeSystem": { ... }, + "pairing": { ... } + } + ``` + + Tournament types: `INDIVIDUAL`, `PAIRGO`, `RENGO2`, `RENGO3`, `TEAM2`, `TEAM3`, `TEAM4`, `TEAM5` + *output* `{ "success": true, "id": #tid }` + `PUT /api/tour/#tid` Modify a tournament *input* json object for updated tournament (only id and updated fields required) - + + *output* `{ "success": true }` + ++ `DELETE /api/tour/#tid` Delete a tournament + *output* `{ "success": true }` ## Players handling + `GET /api/tour/#tid/part` Get a list of registered players - + *output* json array of known players -+ `GET /api/tour/#tid/part/#pid` Get regitration details for player #pid ++ `GET /api/tour/#tid/part/#pid` Get registration details for player #pid *output* json object for player #pid + `POST /api/tour/#tid/part` Register a new player - - *input* `{ "name":"..." , "firstname":"..." , "rating": , "rank": , "country":"XX" [ , "club":"Xxxx" ] [ , "final":true/false ] [ , "mmsCorrection":0 ] }` - + + *input* + ```json + { + "name": "Lastname", + "firstname": "Firstname", + "rating": 1500, + "rank": -5, + "country": "FR", + "club": "Club Name", + "final": true, + "mmsCorrection": 0, + "egfId": "12345678", + "ffgId": "12345", + "agaId": "12345" + } + ``` + + Rank values: -30 (30k) to 8 (9D). Rating in EGF-style (100 = 1 stone). + *output* `{ "success": true, "id": #pid }` + `PUT /api/tour/#tid/part/#pid` Modify a player registration - + *input* json object for updated registration (only id and updated fields required) - + *output* `{ "success": true }` + `DELETE /api/tour/#tid/part/#pid` Delete a player registration - - *input* `{ "id": #pid }` - + *output* `{ "success": true }` ## Teams handling +For team tournaments (PAIRGO, RENGO2, RENGO3, TEAM2-5). + + `GET /api/tour/#tid/team` Get a list of registered teams - + *output* json array of known teams -+ `GET /api/tour/#tid/team/#tid` Get regitration details for team #tid ++ `GET /api/tour/#tid/team/#teamid` Get registration details for team #teamid - *output* json object for team #tid + *output* json object for team #teamid + `POST /api/tour/#tid/team` Register a new team - - *input* json object for new team - - *output* `{ "success": true, "id": #tid }` -+ `PUT /api/tour/#tid/team/#tid` Modify a team registration - + *input* + ```json + { + "name": "Team Name", + "playerIds": [1, 2, 3], + "final": true, + "mmsCorrection": 0 + } + ``` + + *output* `{ "success": true, "id": #teamid }` + ++ `PUT /api/tour/#tid/team/#teamid` Modify a team registration + *input* json object for updated registration (only id and updated fields required) - + *output* `{ "success": true }` -+ `DELETE /api/tour/#tid/team/#tid` Delete a team registration - - *input* `{ "id": #tid }` - ++ `DELETE /api/tour/#tid/team/#teamid` Delete a team registration + *output* `{ "success": true }` ## Pairing + `GET /api/tour/#tid/pair/#rn` Get pairable players for round #rn - - *output* `{ "games": [ games... ], "pairables:" [ #pid, ... of players not skipping and not playing the round ], "unpairables": [ #pid, ... of players skipping the round ] }` -+ `POST /api/tour/#tip/pair/#n` Generate pairing for round #n and given players (or string "all") ; error if already generated for provided players - + *output* + ```json + { + "games": [ { "id": 1, "t": 1, "w": 2, "b": 3, "h": 0 }, ... ], + "pairables": [ 4, 5, ... ], + "unpairables": [ 6, 7, ... ] + } + ``` + + - `games`: existing pairings for the round + - `pairables`: player IDs available for pairing (not skipping, not already paired) + - `unpairables`: player IDs skipping the round + ++ `POST /api/tour/#tid/pair/#rn` Generate pairing for round #rn + *input* `[ "all" ]` or `[ #pid, ... ]` - + + Optional query parameters: + - `legacy=true` - Use legacy pairing algorithm + - `weights_output=` - Output weights to file for debugging + - `append=true` - Append to weights output file + *output* `[ { "id": #gid, "t": table, "w": #wpid, "b": #bpid, "h": handicap }, ... ]` -+ `PUT /api/tour/#tip/pair/#n` Manual pairing (with optional handicap) - ++ `PUT /api/tour/#tid/pair/#rn` Manual pairing or table renumbering + + For manual pairing: *input* `{ "id": #gid, "w": #wpid, "b": #bpid, "h": }` + For table renumbering: + *input* `{ "renumber": , "orderBy": "mms" | "table" }` + *output* `{ "success": true }` -+ `DELETE /api/tour/#tip/pair/#n` Delete pairing for round #n and given players (or string "all") ; games with results entered are skipped - ++ `DELETE /api/tour/#tid/pair/#rn` Delete pairing for round #rn + *input* `[ "all" ]` or `[ #gid, ... ]` - + + Games with results already entered are skipped unless `"all"` is specified. + *output* `{ "success": true }` ## Results -+ `GET /api/tour/#tip/res/#rn` Get results for round #rn - - *output* `[ { "id": #gid, "res": } ]` with `res` being one of: `"w"`, `"b"`, `"="` (jigo), `"x"` (cancelled),`"?"` (unknown), `"#"` (both win), or `"0"` (both loose). - -+ `PUT /api/tour/#tip/res/#rn` Save a result (or put it back to unknown) - - *input* `{ "id": #gid, "res": }` with `res` being one of: `"w"`, `"b"`, `"="` (jigo), `"x"` (cancelled) - ++ `GET /api/tour/#tid/res/#rn` Get results for round #rn + + *output* `[ { "id": #gid, "res": }, ... ]` + + Result codes: + - `"w"` - White won + - `"b"` - Black won + - `"="` - Jigo (draw) + - `"X"` - Cancelled + - `"?"` - Unknown (not yet played) + - `"#"` - Both win (unusual) + - `"0"` - Both lose (unusual) + ++ `PUT /api/tour/#tid/res/#rn` Save a result + + *input* `{ "id": #gid, "res": }` + *output* `{ "success": true }` -+ `DELETE /api/tour/#tip/res/#rn` Clear all results (put back all results to unknown) ++ `DELETE /api/tour/#tid/res/#rn` Clear all results for round - *input* none - - *output* `{ "success": true }` + *output* `{ "success": true }` ## Standings -+ `GET /api/tour/#tid/stand/#rn` Get standings after round #rn (or initial standings for round '0') - - *output* `[ { "id": #pid, "place": place, "": double }, ... ]` - where `` is the name of a criterium, among "score", "nbw", "mms", "sosm", "sososm", ... ++ `GET /api/tour/#tid/standings` Get standings after final round + + *output* `[ { "id": #pid, "place": place, "": value }, ... ]` + + Supports multiple output formats via Accept header: + - `application/json` - JSON (default) + - `application/egf` - EGF format + - `application/ffg` - FFG format + - `text/csv` - CSV format + + Optional query parameters: + - `include_preliminary=true` - Include preliminary standings + - `individual_standings=true` - For team tournaments with individual scoring + ++ `GET /api/tour/#tid/stand/#rn` Get standings after round #rn + + Use round `0` for initial standings. + + *output* `[ { "id": #pid, "place": place, "": value }, ... ]` + + Criteria names include: `nbw`, `mms`, `sts`, `cps`, `sosw`, `sosm`, `sososw`, `sososm`, `sodosw`, `sodosm`, `cussw`, `cussm`, `dc`, `sdc`, `ext`, `exr`, etc. + ++ `PUT /api/tour/#tid/standings` Freeze/lock standings + + *output* `{ "success": true }` + +## Pairing explanation + ++ `GET /api/tour/#tid/explain/#rn` Get detailed pairing criteria weights for round #rn + + *output* Detailed pairing weight analysis and criteria breakdown + + Used for debugging and understanding pairing decisions. ## Authentication -+ `GET /api/token` Get the token of the currently logged user, or give an error. ++ `GET /api/token` Check authentication status -+ `POST /api/token` Create an access token. Expects an authentication json object. + *output* Token information for the currently logged user, or error if not authenticated. -+ `DELETE /api/token` Delete the token of the currently logged user. ++ `POST /api/token` Create an access token + *input* Authentication credentials (format depends on auth mode) + + *output* `{ "success": true, "token": "..." }` + ++ `DELETE /api/token` Logout / revoke token + + *output* `{ "success": true }` diff --git a/doc/configuration.md b/doc/configuration.md index cecded0..cb1614b 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -2,39 +2,96 @@ Pairgoth general configuration is done using the `pairgoth.properties` file in the installation folder. -## environment +Properties are loaded in this order (later overrides earlier): -Controls the running environment: `dev` for development, `prod` for distributed instances. +1. Default properties embedded in WAR/JAR +2. User properties file (`./pairgoth.properties`) in current working directory +3. System properties prefixed with `pairgoth.` (command-line: `-Dpairgoth.key=value`) + +## Environment + +Controls the running environment. ``` env = prod ``` -## mode +Values: +- `dev` - Development mode: enables CORS headers and additional logging +- `prod` - Production: for distributed instances -Running mode: `standalone`, `client` or `server`. +## Mode + +Running mode for the application. ``` mode = standalone ``` -## authentication +Values: +- `standalone` - Both web and API in a single process (default for jar execution) +- `server` - API only +- `client` - Web UI only (connects to remote API) -Authentication: `none`, `sesame` for a shared unique password, `oauth` for email and/or oauth accounts. +## Authentication + +Authentication method for the application. ``` auth = none ``` -When running in client or server mode, if `auth` is not `none`, the following extra property is needed: +Values: +- `none` - No authentication required +- `sesame` - Shared unique password +- `oauth` - Email and/or OAuth accounts + +### Shared secret + +When running in client or server mode with authentication enabled: ``` auth.shared_secret = <16 ascii characters string> ``` -## webapp connector +This secret is shared between API and View webapps. Auto-generated in standalone mode. -Pairgoth webapp connector configuration. +### Sesame password + +When using sesame authentication: + +``` +auth.sesame = +``` + +## OAuth configuration + +When using OAuth authentication: + +``` +oauth.providers = ffg,google,facebook +``` + +Comma-separated list of enabled providers: `ffg`, `facebook`, `google`, `instagram`, `twitter` + +For each enabled provider, configure credentials: + +``` +oauth..client_id = +oauth..secret = +``` + +Example: +``` +oauth.ffg.client_id = your-ffg-client-id +oauth.ffg.secret = your-ffg-client-secret +oauth.google.client_id = your-google-client-id +oauth.google.secret = your-google-client-secret +``` + +## Webapp connector + +Pairgoth webapp (UI) connector configuration. ``` webapp.protocol = http @@ -44,7 +101,10 @@ webapp.context = / webapp.external.url = http://localhost:8080 ``` -## api connector +- `webapp.host` (or `webapp.interface`) - Hostname/interface to bind to +- `webapp.external.url` - External URL for OAuth redirects and client configuration + +## API connector Pairgoth API connector configuration. @@ -56,28 +116,91 @@ api.context = /api api.external.url = http://localhost:8085/api ``` -## store +Note: In standalone mode, API port defaults to 8080 and context to `/api/tour`. -Persistent storage for tournaments, `memory` (mainly used for tests) or `file`. +## SSL/TLS configuration + +For HTTPS connections: + +``` +webapp.ssl.key = path/to/localhost.key +webapp.ssl.cert = path/to/localhost.crt +webapp.ssl.pass = +``` + +Supports `jar:` URLs for embedded resources. + +## Store + +Persistent storage for tournaments. ``` store = file store.file.path = tournamentfiles ``` -## smtp +Values for `store`: +- `file` - Persistent XML files (default) +- `memory` - RAM-based (mainly for tests) -SMTP configuration. Not yet functional. +The `store.file.path` is relative to the current working directory. + +## Ratings + +### Ratings directory ``` -smtp.sender = -smtp.host = +ratings.path = ratings +``` + +Directory for caching downloaded ratings files. + +### Rating sources + +For each rating source (`aga`, `egf`, `ffg`): + +``` +ratings. = +``` + +If not set, ratings are auto-downloaded from the default URL. Set to a local file path to freeze ratings at a specific date. + +Example to freeze EGF ratings: +``` +ratings.egf = ratings/EGF-20240115.json +``` + +### Enable/disable ratings + +``` +ratings..enable = true | false +``` + +Whether to display the rating source button in the Add Player popup. + +``` +ratings..show = true | false +``` + +Whether to show player IDs from this rating source on the registration page. + +Defaults: +- For tournaments in France: FFG enabled and shown by default +- Otherwise: all disabled by default + +## SMTP + +SMTP configuration for email notifications. Not yet functional. + +``` +smtp.sender = sender@example.com +smtp.host = smtp.example.com smtp.port = 587 -smtp.user = -smtp.password = +smtp.user = username +smtp.password = password ``` -## logging +## Logging Logging configuration. @@ -86,34 +209,48 @@ logger.level = info logger.format = [%level] %ip [%logger] %message ``` -## ratings +Log levels: `trace`, `debug`, `info`, `warn`, `error` -Ratings configuration. `` stands for `egf` or `ffg` in the following. +Format placeholders: `%level`, `%ip`, `%logger`, `%message` -### freeze ratings date +## Example configurations -If the following property is given: +### Standalone development -``` -ratings..file = ... +```properties +env = dev +mode = standalone +auth = none +store = file +store.file.path = tournamentfiles +logger.level = trace ``` -then the given ratings file will be used (it must use the Pairgoth ratings json format). If not, the corresponding ratings will be automatically downloaded and stored into `ratings/EGF-yyyymmdd.json` or `ratings/FFG-yyyymmdd.json`. +### Client-server deployment -The typical use case, for a big tournament lasting several days or a congress, is to let Pairgoth download the latest expected ratings, then to add this property to freeze the ratings at a specific date. - -### enable or disable ratings - -Whether to display the EGF or FFG ratings button in the Add Player popup: - -``` -ratings..enable = true | false +**Server (API):** +```properties +env = prod +mode = server +auth = oauth +auth.shared_secret = 1234567890abcdef +api.port = 8085 +store = file +store.file.path = /var/tournaments +logger.level = info ``` -Whether to show the ratings player IDs on the registration page: - +**Client (Web UI):** +```properties +env = prod +mode = client +auth = oauth +auth.shared_secret = 1234567890abcdef +oauth.providers = ffg,google +oauth.ffg.client_id = your-ffg-id +oauth.ffg.secret = your-ffg-secret +oauth.google.client_id = your-google-id +oauth.google.secret = your-google-secret +webapp.port = 8080 +api.external.url = http://api-server:8085/api ``` -ratings..show = true | false -``` - -For a tournament in France, both are true for `ffg` by default, false otherwise. diff --git a/doc/model.md b/doc/model.md index 7a339d2..51c355f 100644 --- a/doc/model.md +++ b/doc/model.md @@ -1,9 +1,7 @@ -# PairGoth model +# Pairgoth Model ## Entity Relationship Diagram -For simplicity, teams (pairgo, rengo) and teams of individuals (clubs championships) are not included. - ```mermaid erDiagram @@ -11,22 +9,23 @@ erDiagram Tournament { int id - string type + Type type string name string shortName date startDate date endDate + string director string country string location - bool isOnline + bool online int rounds int gobanSize - string rules - int komi + Rules rules + double komi } TimeSystem { - string type + TimeSystemType type int mainTime int increment int maxTime @@ -37,18 +36,17 @@ erDiagram Pairing { PairingType type - BaseParams base - MainParams main - SecondaryParams secondary - GeographicalParams geo - HandicapParams handicap - PlacementParams place + PairingParams pairingParams + PlacementParams placementParams } - + Game { + int id int table int handicap - string result + Result result + int drawnUpDown + bool forcedTable } Player { @@ -58,13 +56,26 @@ erDiagram string country string club int rating - string rank + int rank bool final - array skip + int mmsCorrection + set skip + map externalIds } - + + Team { + int id + string name + set playerIds + int rating + int rank + bool final + int mmsCorrection + set skip + } + Standings { - array criteria + list criteria } %% relationships @@ -72,9 +83,266 @@ erDiagram Tournament ||--|{ TimeSystem: "time system" Tournament ||--|{ Pairing: "pairing" Tournament ||--|{ Game: "round" - Tournament }o--|{ Player: "participate(round)" + Tournament }o--|{ Player: "players" + Tournament }o--|{ Team: "teams" + Team }o--|{ Player: "members" Game ||--|| Player: "black" Game ||--|| Player: "white" Player }|--|| Standings: "position" - + ``` + +## Tournament + +Sealed class hierarchy for different tournament formats. + +| Field | Type | Description | +|-------|------|-------------| +| id | int | Tournament identifier | +| type | Type | Tournament format | +| name | string | Full tournament name | +| shortName | string | Abbreviated name | +| startDate | date | Start date | +| endDate | date | End date | +| director | string | Tournament director | +| country | string | Country code (default: "fr") | +| location | string | Venue location | +| online | bool | Is online tournament | +| rounds | int | Total number of rounds | +| gobanSize | int | Board size (default: 19) | +| rules | Rules | Scoring rules | +| komi | double | Komi value (default: 7.5) | +| timeSystem | TimeSystem | Time control | +| pairing | Pairing | Pairing system | +| tablesExclusion | list | Table exclusion rules per round | + +### Tournament Types + +| Type | Players/Team | Description | +|------|--------------|-------------| +| INDIVIDUAL | 1 | Individual players | +| PAIRGO | 2 | Pair Go (alternating) | +| RENGO2 | 2 | Rengo with 2 players | +| RENGO3 | 3 | Rengo with 3 players | +| TEAM2 | 2 | Team with 2 boards | +| TEAM3 | 3 | Team with 3 boards | +| TEAM4 | 4 | Team with 4 boards | +| TEAM5 | 5 | Team with 5 boards | + +### Rules + +- `AGA` - American Go Association +- `FRENCH` - French Go Association +- `JAPANESE` - Japanese rules +- `CHINESE` - Chinese rules + +## Player + +Individual tournament participant. + +| Field | Type | Description | +|-------|------|-------------| +| id | int | Player identifier | +| name | string | Last name | +| firstname | string | First name | +| country | string | Country code | +| club | string | Club affiliation | +| rating | int | EGF-style rating | +| rank | int | Rank (-30=30k to 8=9D) | +| final | bool | Is registration confirmed | +| mmsCorrection | int | MacMahon score correction | +| skip | set | Skipped round numbers | +| externalIds | map | External IDs (AGA, EGF, FFG) | + +## Team + +Team participant (for team tournaments). + +| Field | Type | Description | +|-------|------|-------------| +| id | int | Team identifier | +| name | string | Team name | +| playerIds | set | Member player IDs | +| rating | int | Computed from members | +| rank | int | Computed from members | +| final | bool | Is registration confirmed | +| mmsCorrection | int | MacMahon score correction | +| skip | set | Skipped round numbers | + +## Game + +Single game in a round. + +| Field | Type | Description | +|-------|------|-------------| +| id | int | Game identifier | +| table | int | Table number (0 = unpaired) | +| white | int | White player ID (0 = bye) | +| black | int | Black player ID (0 = bye) | +| handicap | int | Handicap stones | +| result | Result | Game outcome | +| drawnUpDown | int | DUDD value | +| forcedTable | bool | Is table manually assigned | + +### Result + +| Code | Description | +|------|-------------| +| ? | Unknown (not yet played) | +| w | White won | +| b | Black won | +| = | Jigo (draw) | +| X | Cancelled | +| # | Both win (unusual) | +| 0 | Both lose (unusual) | + +## TimeSystem + +Time control configuration. + +| Field | Type | Description | +|-------|------|-------------| +| type | TimeSystemType | System type | +| mainTime | int | Main time in seconds | +| increment | int | Fischer increment | +| maxTime | int | Fischer max time | +| byoyomi | int | Byoyomi time per period | +| periods | int | Number of byoyomi periods | +| stones | int | Stones per period (Canadian) | + +### TimeSystemType + +| Type | Description | +|------|-------------| +| CANADIAN | Canadian byoyomi | +| JAPANESE | Japanese byoyomi | +| FISCHER | Fischer increment | +| SUDDEN_DEATH | No overtime | + +## Pairing + +Pairing system configuration. + +### Pairing Types + +| Type | Description | +|------|-------------| +| SWISS | Swiss system | +| MAC_MAHON | MacMahon system | +| ROUND_ROBIN | Round robin (not implemented) | + +### MacMahon-specific + +| Field | Type | Description | +|-------|------|-------------| +| mmFloor | int | MacMahon floor (default: -20 = 20k) | +| mmBar | int | MacMahon bar (default: 0 = 1D) | + +### Base Parameters + +| Parameter | Description | +|-----------|-------------| +| nx1 | Concavity curve factor (0.0-1.0) | +| dupWeight | Duplicate game avoidance weight | +| random | Randomization factor | +| deterministic | Deterministic pairing | +| colorBalanceWeight | Color balance importance | +| byeWeight | Bye assignment weight | + +### Main Parameters + +| Parameter | Description | +|-----------|-------------| +| categoriesWeight | Avoid mixing categories | +| scoreWeight | Minimize score differences | +| drawUpDownWeight | Draw-up/draw-down weighting | +| compensateDrawUpDown | Enable DUDD compensation | +| drawUpDownUpperMode | TOP, MIDDLE, or BOTTOM | +| drawUpDownLowerMode | TOP, MIDDLE, or BOTTOM | +| seedingWeight | Seeding importance | +| lastRoundForSeedSystem1 | Round cutoff for system 1 | +| seedSystem1 | First seeding method | +| seedSystem2 | Second seeding method | +| mmsValueAbsent | MMS for absent players | +| roundDownScore | Floor vs round scores | + +### Seed Methods + +- `SPLIT_AND_FOLD` +- `SPLIT_AND_RANDOM` +- `SPLIT_AND_SLIP` + +### Secondary Parameters + +| Parameter | Description | +|-----------|-------------| +| barThresholdActive | Don't apply below bar | +| rankSecThreshold | Rank limit for criteria | +| nbWinsThresholdActive | Score threshold | +| defSecCrit | Secondary criteria weight | + +### Geographical Parameters + +| Parameter | Description | +|-----------|-------------| +| avoidSameGeo | Avoid same region | +| preferMMSDiffRatherThanSameCountry | Country preference | +| preferMMSDiffRatherThanSameClubsGroup | Club group preference | +| preferMMSDiffRatherThanSameClub | Club preference | + +### Handicap Parameters + +| Parameter | Description | +|-----------|-------------| +| weight | Handicap minimization weight | +| useMMS | Use MMS vs rank | +| rankThreshold | Rank threshold | +| correction | Handicap reduction | +| ceiling | Max handicap stones | + +## Placement Criteria + +Tiebreak criteria for standings, in order of priority. + +### Score-based + +| Criterion | Description | +|-----------|-------------| +| NBW | Number of wins | +| MMS | MacMahon score | +| STS | Strasbourg score | +| CPS | Cup score | +| SCOREX | Congress score | + +### Opponent-based (W = wins, M = MMS) + +| Criterion | Description | +|-----------|-------------| +| SOSW / SOSM | Sum of opponent scores | +| SOSWM1 / SOSMM1 | SOS minus worst | +| SOSWM2 / SOSMM2 | SOS minus two worst | +| SODOSW / SODOSM | Sum of defeated opponent scores | +| SOSOSW / SOSOSM | Sum of opponent SOS | +| CUSSW / CUSSM | Cumulative score sum | + +### Other + +| Criterion | Description | +|-----------|-------------| +| CATEGORY | Player category | +| RANK | Player rank | +| RATING | Player rating | +| DC | Direct confrontation | +| SDC | Simplified direct confrontation | +| EXT | Exploits attempted | +| EXR | Exploits successful | + +## External Databases + +Player IDs can be linked to external rating databases: + +| Database | Description | +|----------|-------------| +| AGA | American Go Association | +| EGF | European Go Federation | +| FFG | French Go Association |