Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
985fe1d
Small changes from AI: Replaces or supplements message boxes with non…
Chickaboo Aug 25, 2025
15e8ccd
Initial plan
Copilot Sep 1, 2025
70d86b0
Fix layout squishing issue after manual pairing edits
Copilot Sep 1, 2025
b71e99a
Merge pull request #58 from gambit-devs/copilot/fix-38
Chickaboo Sep 1, 2025
5e5a1ad
Adjust margins for ResultSelectorButton
Chickaboo Sep 1, 2025
288a9ce
added .idea to gitignore
nicvagn Sep 4, 2025
d212fcb
Used AI agents to create more tournament intelligent logic
Chickaboo Oct 18, 2025
f573907
Replaces the monolithic TournamentTab and related tab classes with mo…
Chickaboo Dec 22, 2025
f7c3051
Merge pull request #63 from gambit-devs/main
Chickaboo Jan 12, 2026
62f02ee
Fix PyPI publishing configuration: add dynamic version reference and …
Chickaboo Jan 12, 2026
a5cc694
Make build script cross compatible. IE: Check if .exe should be added
nicvagn Jan 22, 2026
f6acd92
Fixup import errors and remove empty unexplained except clauses
nicvagn Jan 22, 2026
0228db8
Merge pull request #66 from gambit-devs/new-logic
nicvagn Jan 22, 2026
b0e6b54
Replace Start Tournament button with Record Results button in toolbar…
Chickaboo Jan 22, 2026
0fe9f4d
Fix UI issues in Rounds tab: increase Board column width, adjust tabl…
Chickaboo Jan 22, 2026
f965d17
Add dynamic toolbar button toggling between Record Results and Prepar…
Chickaboo Jan 22, 2026
0a56938
Ensure consistent single separator in toolbar across platforms by alw…
Chickaboo Jan 22, 2026
6e0f6ef
added a tournament over flag. Needed for some ui stuff
nicvagn Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,5 @@ cython_debug/
# Marimo
marimo/_static/
marimo/_lsp/

.idea
9 changes: 7 additions & 2 deletions make_executable.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,17 @@ def build_executable(spec_file: Path):
["pyinstaller", "--clean", str(spec_file)], f"PyInstaller ({spec_file.name})"
)

name = "gambit-pairing"
# Verify the build
if spec_file.name.endswith("-onedir.spec"):
expected_exe = Path("dist/gambit-pairing/gambit-pairing.exe")
expected_exe = Path("dist") / (
name + (".exe" if sys.platform == "win32" else "")
)
build_type = "onedir"
else:
expected_exe = Path("dist/gambit-pairing.exe")
expected_exe = Path("dist") / (
name + (".exe" if sys.platform == "win32" else "")
)
build_type = "onefile"

if expected_exe.exists():
Expand Down
9 changes: 6 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies = [
"packaging",
"python-dateutil",
"importlib-resources",
"Pillow", # Required for MSI branding generation
"Pillow",
]
license = {text = "GPL-3.0-or-later"}

Expand Down Expand Up @@ -51,8 +51,8 @@ gambit-pairing = "gambitpairing.__main__:main"

[project.urls]
Homepage = "https://www.chickaboo.net/gambit-pairing/"
Repository = "https://github.com/Chickaboo/gambit-pairing"
Issues = "https://github.com/Chickaboo/gambit-pairing/issues"
Repository = "https://github.com/gambit-devs/gambit-pairing"
Issues = "https://github.com/gambit-devs/gambit-pairing/issues"

[project.optional-dependencies]
dev = [
Expand Down Expand Up @@ -87,6 +87,9 @@ ensure_newline_before_comments = true
"gambitpairing.resources" = ["styles.qss"]
"gambitpairing.resources.icons" = ["*.png", "*.ico", "*.webp", "*.svg"]

[tool.setuptools.dynamic]
version = {attr = "gambitpairing.__version__"}

# Added for static type-checking support
[tool.pyright]
include = ["src", "tests"]
Expand Down
2 changes: 1 addition & 1 deletion src/gambitpairing/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
APP_NAME = "Gambit Pairing"
APP_VERSION = "0.6.4 (alpha)"
APP_VERSION = "0.6.4"
__version__ = APP_VERSION
211 changes: 205 additions & 6 deletions src/gambitpairing/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,218 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.


class PairingException(Exception):
"""Error producing pairings"""
# ========== Base Application Exception ==========


class GambitPairingException(Exception):
"""Base exception for all Gambit Pairing errors.

All custom exceptions in the application should inherit from this class.
This enables catching all application-specific errors with a single except clause.
"""

pass


# ========== Pairing Exceptions ==========


class PairingException(GambitPairingException):
"""Base exception for pairing-related errors."""

pass


class InvalidPairingException(PairingException):
"""Raised when a pairing configuration is invalid."""

pass


class RepeatPairingException(PairingException):
"""Raised when attempting to pair players who have already played."""

pass


class NoPairingAvailableException(PairingException):
"""Raised when no valid pairing can be generated."""

pass


# ========== Tournament Exceptions ==========


class TournamentException(GambitPairingException):
"""Base exception for tournament-related errors."""

pass


class TournamentStateException(TournamentException):
"""Raised when tournament is in an invalid state for the requested operation."""

pass


class RoundNotFoundException(TournamentException):
"""Raised when a requested round does not exist."""

pass


class DuplicatePlayerException(TournamentException):
"""Raised when attempting to add a player that already exists."""

pass


# ========== Player Exceptions ==========


class PlayerException(GambitPairingException):
"""Base exception for player-related errors."""

pass


class PlayerNotFoundException(PlayerException):
"""Raised when a requested player cannot be found."""

pass


class InvalidPlayerDataException(PlayerException):
"""Raised when player data is invalid or incomplete."""

pass


# ========== Result Exceptions ==========


class ResultException(GambitPairingException):
"""Base exception for result recording errors."""

pass


class InvalidResultException(ResultException):
"""Raised when a result is invalid (e.g., score out of range)."""

pass


class DuplicateResultException(ResultException):
"""Raised when attempting to record a result that already exists."""

pass


class ResultNotFoundException(ResultException):
"""Raised when a requested result cannot be found."""

pass


# ========== Validation Exceptions ==========


class ValidationException(GambitPairingException):
"""Base exception for validation errors."""

pass


class EmailValidationException(ValidationException):
"""Raised when an email address is invalid."""

pass


class PhoneValidationException(ValidationException):
"""Raised when a phone number is invalid."""

pass


class RatingValidationException(ValidationException):
"""Raised when a rating value is invalid."""

pass


# ========== File/Resource Exceptions ==========


class ResourceException(GambitPairingException):
"""Base exception for resource-related errors."""

pass


class IconException(ResourceException):
"""Raised when there's an error loading or using the app icon."""

pass


class StyleException(ResourceException):
"""Raised when there's an error loading or applying the app style."""

pass


class FileLoadException(ResourceException):
"""Raised when a file cannot be loaded."""

pass


class FileSaveException(ResourceException):
"""Raised when a file cannot be saved."""

pass


# ========== API Exceptions ==========


class APIException(GambitPairingException):
"""Base exception for API-related errors."""

pass


class FIDEAPIException(APIException):
"""Raised when there's an error communicating with the FIDE API."""

pass


class NetworkException(APIException):
"""Raised when there's a network connectivity error."""

pass


# ========== Configuration Exceptions ==========


class ConfigurationException(GambitPairingException):
"""Base exception for configuration errors."""

pass


class IconException(Exception):
"""Error with the app icon"""
class InvalidConfigurationException(ConfigurationException):
"""Raised when configuration data is invalid."""

pass


class StyleException(Exception):
"""Error with the app Style"""
class MissingConfigurationException(ConfigurationException):
"""Raised when required configuration is missing."""

pass
2 changes: 2 additions & 0 deletions src/gambitpairing/gui/dialogs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .manual_pairing_dialog import ManualPairingDialog
from .new_tournament_dialog import NewTournamentDialog
from .player_management_dialog import PlayerManagementDialog
from .print_options_dialog import PrintOptionsDialog
from .tournament_settings_dialoug import SettingsDialog
from .update_dialog import UpdateDownloadDialog
from .update_prompt_dialog import UpdatePromptDialog
Expand All @@ -32,4 +33,5 @@
"UpdateDownloadDialog",
"UpdatePromptDialog",
"AboutDialog",
"PrintOptionsDialog",
]
42 changes: 41 additions & 1 deletion src/gambitpairing/gui/dialogs/manual_pairing_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,9 +797,9 @@ def _create_detachable_player_pool(self):
self.player_pool_dock = QDockWidget("Player Pool", self)

# Enable docking features but disable closing (we'll handle X button differently)
# Disable floating to prevent "detachable" issues
self.player_pool_dock.setFeatures(
QDockWidget.DockWidgetFeature.DockWidgetMovable
| QDockWidget.DockWidgetFeature.DockWidgetFloatable
)

# Allow docking to all sides
Expand Down Expand Up @@ -1507,6 +1507,46 @@ def _auto_pair_remaining(self):
f"Could not auto-pair remaining players: {str(e)}",
)

def _auto_pair_selected_player(self, item):
"""Auto-pair the selected player with the best available opponent."""
player = item.data(Qt.ItemDataRole.UserRole)
if not player:
return

# Find best opponent among available players
available_players = []
for i in range(self.player_pool.count()):
pool_item = self.player_pool.item(i)
if pool_item and not pool_item.isHidden():
pool_player = pool_item.data(Qt.ItemDataRole.UserRole)
if (
pool_player
and pool_player.id != player.id
and pool_player.is_active
):
available_players.append(pool_player)

if not available_players:
QtWidgets.QMessageBox.information(
self, "Auto-Pair", "No available opponents found."
)
return

# Simple logic: find closest rating
best_opponent = min(
available_players, key=lambda p: abs(p.rating - player.rating)
)

self._save_state_for_undo()

# Add to pairings
self.pairings.append((player, best_opponent))

self._populate_player_pool()
self._update_pairings_display()
self._update_stats()
self._update_validation()

def _is_player_available(self, player: Player) -> bool:
"""Check if a player is available for pairing."""
# Withdrawn players are not available for pairing
Expand Down
2 changes: 1 addition & 1 deletion src/gambitpairing/gui/dialogs/new_tournament_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def show_pairing_info(self) -> None:
"details": "<ul><li><b>Pairing Logic:</b> No automatic pairing. TD manually creates all matches each round.</li><li><b>Best For:</b> Special events, demonstration games, custom formats.</li><li><b>Features:</b> Full pairing editor with drag-and-drop, player pool, edit mode for swapping players.</li><li><b>Notes:</b> Maximum flexibility but requires manual work for every round.</li></ul>",
},
}
# Modern dialog with sidebar
# Dialog with sidebar
dialog = QtWidgets.QDialog(self)
dialog.setWindowTitle("About Pairing Systems")
dialog.setMinimumWidth(600)
Expand Down
Loading