Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
Expand Down Expand Up @@ -213,4 +211,5 @@ soccer.json

.vscode/
showoff/
*.json
*.json
.DS_Store
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
# Showoff - A simple sports stats tracker

![Version](https://img.shields.io/badge/version-2.1.1-blue)
![Version](https://img.shields.io/badge/version-2.2-blue)
![Python](https://img.shields.io/badge/python-3.11+-green)
![License](https://img.shields.io/badge/license-GPLv3-orange)
![made with love](https://img.shields.io/badge/made%20with-%3C3-red)

Showoff is a simple sports self-statistics tracker for players or their coaches, written to be easy to use and to be informational.
Showoff - a simple app for saving, viewing and exporting sports stats
Currently supports basketball, soccer.

> Showoff uses [CourtCV](https://github.com/80degree/CourtCV)

## Requirements

1. [Python 3](https://www.python.org/downloads/)
2. Weasyprint
```bash
pip install weasyprint
```

---

Expand All @@ -22,14 +28,6 @@ You can run showoff using [binaries for your system](https://github.com/80degree

> Binaries are an already pre-built packages for your system.

### Binaries status

| Platform | Latest Status | Latest Uploaded |
|----------|-----------------------------|--------------------|
| Windows | Awaits building | v2.0.0 |
| Linux | Ready ✅ | v2.1.1 |
| macOS | Ready ✅ | v2.1.1 |

1. Download the latest binary files for your system:
- [Latest Release](https://github.com/80degree/showoff/releases/latest)

Expand Down Expand Up @@ -61,7 +59,7 @@ You can run showoff using [binaries for your system](https://github.com/80degree
2. Run:

```bash
python3 source/main.py
python3 main.py
```

---
Expand All @@ -79,7 +77,7 @@ If you want to build yours binary:
2. Build:

```bash
python -m nuitka --onefile --standalone --include-package-data=locales source/main.py
python -m nuitka --onefile --standalone --include-package-data=locales main.py
```

3. A ready binary will be built in the same folder
Expand Down
4 changes: 3 additions & 1 deletion source/__init__.py → __init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
VERSION = '2.1.1'
import pathlib
VERSION = '2.2'
LOGO = """
▗▄▄▖▐▌ ▄▄▄ ▄ ▄ ▄▄▄ ▗▞▀▀▘▗▞▀▀▘
▐▌ ▐▌ █ █ █ ▄ █ █ █ ▐▌ ▐▌
Expand All @@ -11,3 +12,4 @@
{LOGO}
v{VERSION} - worthyworm 2026
"""
RUNNING_DIR = pathlib.Path().resolve()
19 changes: 10 additions & 9 deletions source/data_handler.py → data_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import json
import sys
from ui_handler import texts
from __init__ import VERSION
from __init__ import VERSION, RUNNING_DIR

sport = 0

try:
with open('preferences.json', 'r', encoding='utf-8') as f:
with open(f'{RUNNING_DIR}/preferences.json', 'r', encoding='utf-8') as f:
preferences = json.load(f)
if preferences['sport'] == 1:
sport = 1
Expand All @@ -29,7 +29,7 @@
if sport > 2 or sport < 1:
print("Invalid option")
else:
with open('preferences.json', 'w', encoding='utf-8') as f:
with open(f'{RUNNING_DIR}/preferences.json', 'w', encoding='utf-8') as f:
preferences['sport'] = sport
json.dump(preferences, f, ensure_ascii=False, indent=4)
break
Expand All @@ -46,11 +46,10 @@

positions_basketball = ['PG', 'SG', 'SF', 'PF', 'C']
positions_soccer = ['GK', 'SW', 'LB', 'CB', 'RB', 'LWB', 'RWB', 'DM', 'LM', 'CM', 'RM', 'AM', 'LW', 'SS', 'RW', 'LF', 'CF', 'RF']
#positions_voleyball = ['OH', 'OPP', 'MB', 'S', 'L', 'DS', 'SS']

if sport == 1:
try:
with open('basketball.json', 'r', encoding='utf-8') as f:
with open(f'{RUNNING_DIR}/basketball.json', 'r', encoding='utf-8') as f:
db = json.load(f)
if tuple(map(int, db['version'].split('.'))) < (2, 0, 0):
print(f'{texts["unsupported_version"]}: {db["version"]}version')
Expand All @@ -70,7 +69,7 @@

elif sport == 2:
try:
with open('soccer.json', 'r', encoding='utf-8') as f:
with open(f'{RUNNING_DIR}/soccer.json', 'r', encoding='utf-8') as f:
db = json.load(f)
if tuple(map(int, db['version'].split('.'))) < (2, 0, 0):
print(f'{texts["unsupported_version"]}: {db["version"]}')
Expand All @@ -86,7 +85,7 @@


def change_sport(preferences):
with open('preferences.json', 'w', encoding='utf-8') as f:
with open(f'{RUNNING_DIR}/preferences.json', 'w', encoding='utf-8') as f:
if preferences['sport'] == 1:
preferences['sport'] = 2
json.dump(preferences, f, ensure_ascii=False, indent=4)
Expand All @@ -103,6 +102,7 @@ def add_match():
new_game = {
"name": input(f"{texts["game_name"]}: "),
"date": input(f"{texts["game_date"]}: "),
"opponent": input("Opponent: "),
"position": positions_basketball[int(input(f'{texts["game_basketball_position"]}\n>>> ').strip()) - 1],
"minutes": int(input(f'{texts["game_minutes"]}: ')),
"points": int(input(f"{texts["game_points"]}: ")),
Expand Down Expand Up @@ -139,6 +139,7 @@ def add_match():
new_game = {
"name": input(f"{texts["game_name"]}: "),
"date": input(f"{texts["game_date"]}: "),
"opponent": input("Opponent: "),
"position": positions_soccer[int(input(f'{texts["game_soccer_position"]}\n>>> ').strip()) - 1],
"minutes": int(input(f'{texts["game_minutes"]}: ')),
"goals": int(input(f'{texts["game_goals"]}: ')),
Expand Down Expand Up @@ -170,7 +171,7 @@ def save():

if sport == 1:
try:
with open('basketball.json', 'w', encoding='utf-8') as f:
with open(f'{RUNNING_DIR}/basketball.json', 'w', encoding='utf-8') as f:
json.dump(db, f, ensure_ascii=False, indent=4)
except FileNotFoundError:
print('File basketball.json does not exist.')
Expand All @@ -180,7 +181,7 @@ def save():
input(f'{texts["enter_to_continue"]}')
if sport == 2:
try:
with open('soccer.json', 'w', encoding='utf-8') as f:
with open(f'{RUNNING_DIR}/soccer.json', 'w', encoding='utf-8') as f:
json.dump(db, f, ensure_ascii=False, indent=4)
except FileNotFoundError:
print('File soccer.json does not exist.')
Expand Down
139 changes: 139 additions & 0 deletions export_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# export_handler is responsible for exporting
import csv
import json
from datetime import datetime
from ui_handler import texts

now = datetime.now()


def export(sport, isCourtCV, filename=f'export_{now.strftime("%d%m%Y_%H%M%S")}.csv'):
if not isCourtCV:
if sport == 1:
try:
with open('basketball.json', 'r', encoding='utf-8') as f:
data = json.load(f)
except FileNotFoundError:
print(f"{texts["file_not_found"]}: basketball.json")
input(f'{texts["enter_to_continue"]}...')
return
except Exception as e:
print(f'ERROR: {e}')
input(f'{texts["enter_to_continue"]}...')
return

games = data['games']
if not games:
print(f'{texts["no_games"]}')
return

headers = ['Date', 'Opponent', 'POS', 'MIN', 'PTS', 'AST', '2PTA', '2PTM', '3PTA', '3PTM', 'REB', 'BLK', 'STL', 'PF',
'Missed Free Throws', 'Turnovers', 'Result']

key_mapping = {
'Date': 'date',
'Opponent': 'opponent',
'POS': 'position',
'MIN': 'minutes',
'PTS': 'points',
'AST': 'assists',
'2PTA': '2pt_attempts',
'2PTM': '2pt_shots_made',
'3PTA': '3pt_attempts',
'3PTM': '3pt_shots_made',
'REB': 'rebounds',
'BLK': 'blocks',
'STL': 'steals',
'PF': 'personal_fouls',
'Missed Free Throws': 'missed_free_throws',
'Turnovers': 'turnovers',
'Result': 'result'
}

with open(filename, 'w', newline='', encoding='utf-8') as csvf:
writer = csv.DictWriter(csvf, fieldnames=headers)
writer.writeheader()
for game in games:
row = {csv_key: game.get(json_key, '') for csv_key, json_key in key_mapping.items()}
writer.writerow(row)

if sport == 2:
try:
with open('soccer.json', 'r', encoding='utf-8') as f:
data = json.load(f)
except FileNotFoundError:
print(f"{texts["file_not_found"]}: soccer.json")
input(f'{texts["enter_to_continue"]}...')
return
except Exception as e:
print(f'ERROR: {e}')
input(f'{texts["enter_to_continue"]}...')
return

games = data['games']
if not games:
print(f'{texts["no_games"]}')
return

headers = ['Date', 'Opponent', 'POS', 'MIN', 'GOALS', 'AST', 'SHOTS', 'SAVES', 'STEALS', 'Yellow cards', 'Red cards', 'Fouls', 'Result']

key_mapping = {
'Date': 'date',
'Opponent': 'opponent',
'POS': 'position',
'MIN': 'minutes',
'GOALS': 'goals',
'AST': 'assists',
'SHOTS': 'shots',
'SAVES': 'saves',
'STEALS': 'steals',
'Yellow cards': 'yellow_cards',
'Red cards': 'red_cards',
'Fouls': 'fouls',
'Result': 'result'
}

with open(filename, 'w', newline='', encoding='utf-8') as csvf:
writer = csv.DictWriter(csvf, fieldnames=headers)
writer.writeheader()
for game in games:
row = {csv_key: game.get(json_key, '') for csv_key, json_key in key_mapping.items()}
writer.writerow(row)

print(f'{texts["export_finished"]} {filename}')
input(f'{texts["enter_to_continue"]}...')
else: #ccv mode
try:
with open('basketball.json', 'r', encoding='utf-8') as f:
data = json.load(f)
except FileNotFoundError:
print(f"{texts["file_not_found"]}: basketball.json")
input(f'{texts["enter_to_continue"]}...')
return
except Exception as e:
print(f'ERROR: {e}')
input(f'{texts["enter_to_continue"]}...')
return

games = data['games']
if not games:
print(f'{texts["no_games"]}')
return

headers = ['Date', 'Opponent', 'PTS', 'REB', 'AST', 'MIN']

key_mapping = {
'Date': 'date',
'Opponent': 'opponent',
'PTS': 'points',
'REB': 'rebounds',
'AST': 'assists',
'MIN': 'minutes',
}

with open('courtcv_input.csv', 'w', newline='', encoding='utf-8') as csvf:
writer = csv.DictWriter(csvf, fieldnames=headers)
writer.writeheader()
for game in games:
row = {csv_key: game.get(json_key, '') for csv_key, json_key in key_mapping.items()}
writer.writerow(row)
Loading
Loading