Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge latest development to master #9

Merged
merged 25 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
66 changes: 66 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: CI

on:
push:
branches:
- master
- development
pull_request:
branches:
- master
- development

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[dev]

- name: Run flake8
run: |
echo "$PWD"
flake8 . --config=setup.cfg --count --show-source --statistics

test:
name: Test
needs: lint
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest] # TODO: Test on macos-latest when repo is public
python-version: ["3.7", "3.8", "3.9", "3.10"] # TODO: Add 3.11 after `wrapt` update: https://github.com/GrahamDumpleton/wrapt/issues/226
permissions:
contents: read

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[dev]

- name: Run pytest
run: |
pytest
41 changes: 0 additions & 41 deletions .github/workflows/test.yml

This file was deleted.

11 changes: 2 additions & 9 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
config.ini
*.swp
*.~
*.spec
.coverage
*.pyc
*.egg-info
*.spec
*.eggs/
.coverage
.venv/
.vscode/
.idea/
venv/
dist/
build/
engines/
__pycache__/
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ A highly customizable way to play chess in your terminal. Supports online play (
offline play against the Fairy-Stockfish engine. All Lichess variants are supported.
</p>

<p align="center">
<a href="https://github.com/trevorbayless/cli-chess/actions/">
<img alt="CI Workflow" src="https://github.com/trevorbayless/cli-chess/actions/workflows/ci.yml/badge.svg?branch=master?event=push">
</a>
<a href="https://pypi.org/project/cli-chess/">
<img alt="PyPI" src="https://img.shields.io/pypi/v/cli-chess?color=informational&label=PyPI&logo=PyPI">
</a>
</p>

## Main Features
- Play online using your Lichess.org account
- Play offline against the Fairy-Stockfish engine
Expand Down
5 changes: 5 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ where = src
[options.entry_points]
console_scripts =
cli-chess = cli_chess.__main__:run

[flake8]
max-line-length = 150
per-file-ignores =
*/__init__.py: F401
7 changes: 5 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@
dependencies = [
"chess>=1.9.4,<2.0.0",
"berserk-downstream>=0.11.12,<1.0.0",
"prompt-toolkit>=3.0.36,<4.0.0"
"prompt-toolkit==3.0.38" # pin as breaking changes have been
# introduced in previous patch versions
# read PT changelog before bumping
]

dev_dependencies = {
'dev': [
'pytest>=7.2.1,<8.0.0',
'pytest-cov>=4.0.0,<5.0.0',
'flake8>=5.0.4,<7.0.0'
'flake8>=5.0.4,<7.0.0',
'vulture>=2.7,<3.0'
]
}

Expand Down
21 changes: 14 additions & 7 deletions src/cli_chess/core/api/api_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,7 @@
from berserk import Client, TokenSession
from typing import Optional

API_TOKEN_CREATION_URL = ("https://lichess.org/account/oauth/token/create?" +
"scopes[]=challenge:read&" +
"scopes[]=challenge:write&" +
"scopes[]=board:play&" +
"description=cli-chess+token")

required_token_scopes: set = {"board:play", "challenge:read", "challenge:write"}

api_session: Optional[TokenSession]
api_client: Optional[Client]
api_iem: Optional[IncomingEventManager]
Expand Down Expand Up @@ -54,3 +47,17 @@ def api_is_ready() -> bool:
this is used for toggling the online menu availability
"""
return api_ready


def _create_api_token_url() -> str:
"""Created the API token creation url by iterating over scopes"""
url = "https://lichess.org/account/oauth/token/create?"

for scope in required_token_scopes:
url = url + f"scopes[]={scope}&"

url = url + "description=cli-chess+token"
return url


API_TOKEN_CREATION_URL = _create_api_token_url()
25 changes: 16 additions & 9 deletions src/cli_chess/core/api/game_state_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from cli_chess.utils import Event, log
from typing import Callable
import threading


Expand Down Expand Up @@ -47,10 +48,12 @@ def run(self):
self.e_game_state_dispatcher_event.notify(gameFull=event)

elif event['type'] == "gameState":
self.e_game_state_dispatcher_event.notify(gameState=event)
is_game_over = event.get('winner')
status = event.get('status', None)
is_game_over = status and status != "started" and status != "created"

self.e_game_state_dispatcher_event.notify(gameState=event, gameOver=is_game_over)
if is_game_over:
break
self._game_ended()

elif event['type'] == "chatLine":
self.e_game_state_dispatcher_event.notify(chatLine=event)
Expand All @@ -76,27 +79,31 @@ def make_move(self, move: str):
def send_takeback_request(self) -> None:
"""Sends a takeback request to our opponent"""
try:
log.debug(f"GameStateDispatcher: Sending takeback offer to opponent")
log.debug("GameStateDispatcher: Sending takeback offer to opponent")
self.api_client.board.offer_takeback(self.game_id)
except Exception:
raise

def send_draw_offer(self) -> None:
"""Sends a draw offer to our opponent"""
try:
log.debug(f"GameStateDispatcher: Sending draw offer to opponent")
log.debug("GameStateDispatcher: Sending draw offer to opponent")
self.api_client.board.offer_draw(self.game_id)
except Exception:
raise

def resign(self) -> None:
"""Resigns the game"""
try:
log.debug(f"GameStateDispatcher: Sending resignation")
log.debug("GameStateDispatcher: Sending resignation")
self.api_client.board.resign_game(self.game_id)
except Exception:
raise

def clear_listeners(self) -> None:
"""Remove all event listeners"""
self.e_game_state_dispatcher_event.listeners.clear()
def _game_ended(self) -> None:
"""Handles removing all event listeners since the game has completed"""
self.e_game_state_dispatcher_event.remove_all_listeners()

def subscribe_to_events(self, listener: Callable) -> None:
"""Subscribes the passed in method to GSD events"""
self.e_game_state_dispatcher_event.add_listener(listener)
9 changes: 9 additions & 0 deletions src/cli_chess/core/api/incoming_event_manger.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from cli_chess.utils.event import Event
from cli_chess.utils.logging import log
from typing import Callable
import threading


Expand Down Expand Up @@ -77,3 +78,11 @@ def run(self) -> None:
def get_active_games(self) -> list:
"""Returns a list of games in progress for this account"""
return self.my_games

def subscribe_to_iem_events(self, listener: Callable) -> None:
"""Subscribes the passed in method to IEM events"""
self.e_new_event_received.add_listener(listener)

def unsubscribe_from_iem_events(self, listener: Callable) -> None:
"""Unsubscribes the passed in method to IEM events"""
self.e_new_event_received.remove_listener(listener)
31 changes: 25 additions & 6 deletions src/cli_chess/core/game/game_model_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from cli_chess.modules.board import BoardModel
from cli_chess.modules.move_list import MoveListModel
from cli_chess.modules.material_difference import MaterialDifferenceModel
from cli_chess.utils.event import Event
from cli_chess.utils import EventManager, log
from chess import Color, WHITE, COLOR_NAMES
from random import getrandbits
from abc import ABC, abstractmethod
Expand All @@ -30,9 +30,13 @@ def __init__(self, orientation: Color = WHITE, variant="standard", fen=""):
self.move_list_model = MoveListModel(self.board_model)
self.material_diff_model = MaterialDifferenceModel(self.board_model)

self.e_game_model_updated = Event()
self._event_manager = EventManager()
self.e_game_model_updated = self._event_manager.create_event()
self.board_model.e_board_model_updated.add_listener(self.update)

# Keep track of all associated models to handle bulk cleanup on exit
self._assoc_models = [self.board_model, self.move_list_model, self.material_diff_model]

def update(self, **kwargs) -> None:
"""Called automatically as part of an event listener. This function
listens to model update events and if deemed necessary triages
Expand All @@ -41,6 +45,19 @@ def update(self, **kwargs) -> None:
if 'board_orientation' in kwargs:
self._notify_game_model_updated(**kwargs)

def cleanup(self) -> None:
"""Cleans up after this model by clearing all associated models event listeners.
This should only ever be run when the models are no longer needed.
"""
self._event_manager.purge_all_events()

# Notify associated models to clean up
for model in self._assoc_models:
try:
model.cleanup()
except AttributeError:
log.error(f"GameModelBase: {model} does not have a cleanup method")

def _notify_game_model_updated(self, **kwargs) -> None:
"""Notify listeners that the model has updated"""
self.e_game_model_updated.notify(**kwargs)
Expand All @@ -51,21 +68,19 @@ def _default_game_metadata() -> dict:
return {
'gameId': "",
'variant': "",
'winner': "",
'status': "",
'players': {
'white': {
'title': "",
'name': "",
'rating': "",
'ratingDiff': "",
'rating_diff': "",
'provisional': False,
},
'black': {
'title': "",
'name': "",
'rating': "",
'ratingDiff': "",
'rating_diff': "",
'provisional': False,
},
},
Expand All @@ -79,6 +94,10 @@ def _default_game_metadata() -> dict:
'increment': 0
},
},
'state': {
'status': "",
'winner': "",
}
}


Expand Down
Loading