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
23 changes: 23 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: Bug report
about: Create a bug report to help us improve
title: "[BUG] title of bug"
labels: bug
assignees: ''

---

### Describe the bug
A short 1-2 sentences describing briefly what the bug is.

### Bug Description
#### To Reproduce
- List the steps you took to find the issue (preferably from fresh install, but also just from application startup is fine
#### Expected behavior
- List what the expected behavior is
### Screenshots
Desktop
OS: *Insert your OS here*

### Additional context
Write additional notes, or things you noticed / might be different about your setup
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Pycharm configuration settings (can unignore in future if important)
.idea/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
35 changes: 35 additions & 0 deletions docs/make.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)

if "%1" == "" goto help

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd
31 changes: 31 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = 'quackbox'
copyright = "2025, Sean O'Donnell"
author = "Sean O'Donnell"
release = '0.1.1'

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
import sys
from pathlib import Path
sys.path.insert(0, str(Path('..', '..', 'src').resolve()))

extensions = ['sphinx.ext.autodoc',]

templates_path = ['_templates']
exclude_patterns = []



# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = 'alabaster'
html_static_path = ['_static']
23 changes: 23 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.. quackbox documentation master file, created by
sphinx-quickstart on Tue May 13 16:43:46 2025.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.

quackbox documentation
======================

Add your content using ``reStructuredText`` syntax. See the
`reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_
documentation for details.


.. toctree::
:maxdepth: 2
:caption: Contents:

.. automodule:: quackbox.leaderboard
:members:

.. automodule:: quackbox.save_data
:members:

51 changes: 44 additions & 7 deletions src/quackbox/leaderboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import requests

_ROUTE = "http://127.0.0.1:6174/api/v1/leaderboard"
MAX_PLAYERS = 8


@dataclass
Expand All @@ -22,25 +23,61 @@ def from_dict(d: dict) -> "LeaderboardEntry":

def add_leaderboard_entry(value_name: str, value_num: float,
player_slot: int = 1):
payload = {
"value_name": value_name,
"value_num": value_num,
"player_slot": player_slot,
}
"""
Insert a leaderboard entry into the QuackBox database.

:param value_name: The name of the value to be store i.e. Time, High Score, etc.
This name will be displayed at the top of any leaderboard tables
on the QuackBox.
:param value_num: The numeric value of the leaderboard entry. These values are
stored internally as 64-bit floats.
:param player_slot: The controller slot of the player for this data to be associated with.
The slots begin at number 1 and go up to number 8.

:raises AssertionError: If the player_slot is not a valid slot.
"""
assert 1 <= player_slot <= MAX_PLAYERS
payload = {"value_name": value_name, "value_num": value_num,
"player_slot": player_slot, }

requests.post(_ROUTE, json=payload)


def get_user_lb_entries(value_name: str, count: int = 100,
player_slot: int = 1) -> List[
LeaderboardEntry]:
player_slot: int = 1) -> List[LeaderboardEntry]:
"""
Retrieve the leaderboard entries associated with a specific user in descending
order according to the value stored.

:param value_name: The name of the leaderboard to be retrieved. This is the
same name that is displayed at the top of any leaderboard tables on the QuackBox.
:param count: The number of entries to retrieve.
:param player_slot: The controller slot of the player to retrieve entries for.
The slots begin at number 1 and go up to number 8.

:return: A list of LeaderboardEntry objects that contain a name, a value, and
a timestamp.

:raises AssertionError: if the player_slot is not a valid slot.
"""
assert 1 <= player_slot <= MAX_PLAYERS
params = {"value_name": value_name, "count": count,
"player_slot": player_slot}
response = requests.get(_ROUTE, params=params)
return [LeaderboardEntry.from_dict(d) for d in response.json()]


def get_global_lb_entries(value_name: str, count: int = 100):
"""
Retrieve the top leaderboard entries with a specific name in descending order.

:param value_name: The name of the leaderboard to be retrieved. This is the
same name that is displayed at the top of any leaderboard tables on the QuackBox.
:param count: The number of entries to retrieve.

:return: A list of LeaderboardEntry objects that contain a name, a value, and
a timestamp.
"""
params = {"value_name": value_name, "count": count, "scope": "global"}
response = requests.get(_ROUTE, params=params)
return [LeaderboardEntry.from_dict(d) for d in response.json()]
59 changes: 59 additions & 0 deletions src/quackbox/save_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import requests

_ROUTE = "http://127.0.0.1:6174/api/v1/save-data"
MAX_PLAYERS = 8


@dataclass
Expand All @@ -20,12 +21,40 @@ def from_dict(d: dict):


def get_save_file_names(player_slot: int = 1) -> List[str]:
"""
Retrieves all save file names from the QuackBox for a specific player.

:param player_slot: The controller slot of the player to retrieve entries for.
The slots begin at number 1 and go up to number 8.

:return: A list of save file names in the same format as they were stored using
functions such as :py:func: `add_save_data()`.

:raises AssertionError: If player_slot is not a valid controller slot.
"""
assert 1 <= player_slot <= MAX_PLAYERS

params = {"player_slot": player_slot}
response = requests.get(_ROUTE, params=params)
return [d["file_name"] for d in response.json()]


def get_save_data(regex: str, player_slot: int = 1) -> List[SaveDataEntry]:
"""
Retrieves all save data from the QuackBox for a specific player.

:param regex: A regular expression pattern to match save data file names against.
:param player_slot: The controller slot of the player to retrieve entries for.
The slots begin at number 1 and go up to number 8.

:return: A list of save data entries containing a string representation of the
save data. This is in the same format as the data wes stored using
functions such as :py:func: `add_save_data()`.

:raises AssertionError: If player_slot is not a valid controller slot.
"""
assert 1 <= player_slot <= MAX_PLAYERS

# Check that regex is valid
re.compile(regex)

Expand All @@ -35,11 +64,41 @@ def get_save_data(regex: str, player_slot: int = 1) -> List[SaveDataEntry]:


def get_save_data_file(file_name: str, player_slot: int = 1) -> SaveDataEntry:
"""
Retrieves a single save data file from the QuackBox.

:param file_name: The name of the file to retrieve.
:param player_slot: The controller slot of the player to retrieve entries for.
The slots begin at number 1 and go up to number 8.

:return: A single save data entry containing a string representation of the
save data. This is in the same format as the data wes stored using
methods such as :py:func: `add_save_data()`.

:raises AssertionError: If player_slot is not a valid controller slot.
"""
assert 1 <= player_slot <= MAX_PLAYERS

response = requests.get(_ROUTE, params={"file_name": file_name,
"player_slot": player_slot})
return SaveDataEntry.from_dict(response.json()[0])


def add_save_data(file_name: str, data: str, player_slot: int = 1):
"""
Insert new save file into the QuackBox database. This will overwrite any file
stored with the same file_name.

:param file_name: The name of the file to insert.
:param data: A string representation of the save data. This can be in any format
and be returned as it is stored when using functions such as
:py:func:`get_save_data()`.
:param player_slot: The controller slot of the player to retrieve entries for.
The slots begin at number 1 and go up to number 8.

:raises AssertionError: If player_slot is not a valid controller slot.
"""
assert 1 <= player_slot <= MAX_PLAYERS

payload = {"file_name": file_name, "data": data, "player_slot": player_slot}
requests.post(_ROUTE, json=payload)