Skip to content

Commit

Permalink
Merge latest development to master
Browse files Browse the repository at this point in the history
  • Loading branch information
trevorbayless authored Mar 2, 2023
2 parents 5aa3381 + 95f1b90 commit 0c06ddb
Show file tree
Hide file tree
Showing 43 changed files with 432 additions and 258 deletions.
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

0 comments on commit 0c06ddb

Please sign in to comment.