- Gear icon in header opens settings modal - Preference stored in cookie for server-side Velocity rendering - ViewServlet reads blackFirst cookie into Velocity context - Velocity conditionals in pairing, results, and result-sheets templates
5.9 KiB
5.9 KiB
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
- No JS Framework - 2200 lines of vanilla JS vs typical 50KB+ bundle
- Fomantic CSS Only - Using CSS framework without its jQuery-dependent JS
- CSS @layer - Clean cascade:
semanticlayer <pairgothlayer - File Storage - XML files for portability, no database setup needed
- Read/Write Locks - Simple concurrency on API servlet
- 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
# 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)
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
// Custom events
box.dispatchEvent(new CustomEvent('listitem-dblclk', { detail: id }));
// jQuery-like API (domhelper.js)
$('.item').addClass('active').on('click', handler);
API Integration
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)
Current Work
User Preferences (feature/user-preferences branch)
Implemented "black vs white" display order option:
- Gear icon in header opens settings modal
- Preference stored in cookie (
blackFirst) for server-side Velocity rendering - localStorage backup via store2 (
prefs.blackFirst) - Velocity conditionals in tour-pairing.inc.html, tour-results.inc.html, result-sheets.html
- ViewServlet reads cookie and sets
$blackFirstin Velocity context
Files modified:
view-webapp/.../layouts/standard.html- gear icon + settings modalview-webapp/.../sass/main.scss- settings modal stylesview-webapp/.../js/main.js- prefs object + modal handlers + cookie setview-webapp/.../kotlin/.../ViewServlet.kt- read blackFirst cookieview-webapp/.../tour-pairing.inc.html-#if($blackFirst)conditionalsview-webapp/.../tour-results.inc.html-#if($blackFirst)conditionals + inverted result displayview-webapp/.../result-sheets.html-#if($blackFirst)conditionals