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
108 changes: 108 additions & 0 deletions .github/workflows/test_and_build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Test and Build

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
inputs:
version:
description: 'Version to publish (leave empty to use current version)'
required: false
type: string
publish:
description: 'Publish to PyPI'
required: false
default: false
type: boolean

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.13"]

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: |
uv sync --dev

- name: Lint with ruff
run: |
uv run ruff check .
uv run ruff format --check .

- name: Run tests
run: |
uv run pytest -v

- name: Upload coverage reports
uses: codecov/codecov-action@v4
if: matrix.python-version == '3.11'
with:
file: ./htmlcov/index.html
fail_ci_if_error: false

build:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"

- name: Set up Python
run: uv python install 3.11

- name: Update version if specified
if: ${{ github.event.inputs.version != '' }}
run: |
sed -i 's/version = "[^"]*"/version = "${{ github.event.inputs.version }}"/' pyproject.toml
echo "Updated version to ${{ github.event.inputs.version }}"

- name: Build package
run: |
uv build

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
needs: [test, build]
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true'
environment: release
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing

steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ SHAPE is an app to play Go with AI feedback, specifically designed to point out

This is an experimental project, and is unlikely to ever become very polished.

## Installation
## Quick Start

* Run install.sh to download the models.
* Run `poetry shell` and then `poetry install` to install the app in a local Python environment.
* Alternatively, run `pip install .` to install the app in your current Python environment.
Run the application directly using `uvx`:

## Usage
```bash
uvx goshape
```

* Run `shape` to start the app, or use `python shape/main.py`
The first time you run this, `uv` will automatically download the package, create a virtual environment, and install all dependencies.

When the application starts for the first time, it will check for the required KataGo models in `~/.katrain/`. If they are not found, a dialog will appear to guide you through downloading them.

## Manual

Expand All @@ -35,3 +37,16 @@ Note that a move being probable does not mean it is a good move.
You can select multiple heatmaps to get a blended view, where size/number is the average probability, and the color is the average rank (current, target, AI).


## TODO list from Gemini

Based on a code review, here are some suggested areas for improvement:

### High Impact
- **User-Friendly Errors (`main.py`):** Show GUI dialogs for errors instead of crashing the application.

### Medium Impact
- **Refactor `GameNode` (`game_logic.py`):** Extract board state and rule logic into a separate `Board` class to simplify `GameNode` and improve modularity.

### Low Impact
- **Code Clarity (`game_logic.py`):** Improve code readability.

2 changes: 0 additions & 2 deletions install.sh

This file was deleted.

67 changes: 48 additions & 19 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,29 +1,58 @@
[tool.poetry]
name = "shape"
[project]
name = "goshape"
version = "0.1.0"
description = ""
authors = ["Sander Land"]
description = "Shape Habits Analysis and Personalized Evaluation"
authors = [{name = "Sander Land"}]
readme = "README.md"
requires-python = ">=3.10,<3.14"
dependencies = [
"PySide6>=6.5.0",
"pyqtgraph>=0.13.7",
"pysgf>=0.9.0",
"numpy>=2.1.2",
"httpx>=0.25.0",
]

[tool.poetry.dependencies]
python = "^3.10,<3.13"
PySide6 = "^6.5.0"
pyqtgraph = "^0.13.7"
pysgf = "^0.9.0"
numpy = "^2.1.2"
[dependency-groups]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"ruff>=0.1.0",
]

[tool.vulture]
ignore_names = ["paintEvent", "keyPressEvent", "mousePressEvent"]
[project.scripts]
goshape = "shape.main:main"

[tool.hatch.build.targets.wheel]
packages = ["shape"]

[tool.black]
[tool.ruff]
line-length = 120
target-version = "py310"

[tool.isort]
profile = "black"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
ignore = [
"E501", # line too long, handled by formatter
]

[tool.poetry.scripts]
shape = "shape.main:main"
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

[tool.vulture]
ignore_names = ["paintEvent", "keyPressEvent", "mousePressEvent"]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["hatchling"]
build-backend = "hatchling.build"
4 changes: 2 additions & 2 deletions shape/game_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def sample(
secondary_data_data = secondary_data if secondary_data is not None else self.grid
moves = [
(Move(coords=(col, row)), prob, d)
for row, (policy_row, secondary_data_row) in enumerate(zip(self.grid, secondary_data_data))
for col, (prob, d) in enumerate(zip(policy_row, secondary_data_row))
for row, (policy_row, secondary_data_row) in enumerate(zip(self.grid, secondary_data_data, strict=False))
for col, (prob, d) in enumerate(zip(policy_row, secondary_data_row, strict=False))
if prob > 0
]
if self.pass_prob > 0 and not exclude_pass:
Expand Down
Loading