Skip to content

Commit

Permalink
Online game event rework
Browse files Browse the repository at this point in the history
  • Loading branch information
trevorbayless committed Jun 11, 2024
1 parent dd6cee4 commit 5f1a8b1
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 166 deletions.
46 changes: 30 additions & 16 deletions src/cli_chess/core/api/game_state_dispatcher.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
from cli_chess.utils import Event, log, retry
from cli_chess.utils import Event, EventTopics, log, retry
from typing import Callable
from threading import Thread
from enum import Enum, auto
from types import MappingProxyType


class GSDEventTopics(Enum):
CHAT_RECEIVED = auto()
OPPONENT_GONE = auto()
NOT_IMPLEMENTED = auto()


gsd_type_to_event_dict = MappingProxyType({
"gameFull": EventTopics.GAME_START,
"gameState": EventTopics.MOVE_MADE,
"chatLine": GSDEventTopics.CHAT_RECEIVED,
"opponentGone": GSDEventTopics.OPPONENT_GONE,
})


class GameStateDispatcher(Thread):
"""Handles streaming a game and sending game commands (make move, offer draw, etc)
using the Board API. The game that is streamed using this class must be owned
by the account linked to the api token.
"""

def __init__(self, game_id=""):
super().__init__()
self.game_id = game_id
self.is_game_over = False
self.e_game_state_dispatcher_event = Event()

try:
Expand All @@ -29,22 +45,14 @@ def run(self):
log.info(f"Started streaming game state: {self.game_id}")

for event in self.api_client.board.stream_game_state(self.game_id):
log.debug(f"Stream event received: {event['type']}")
if event['type'] == "gameFull":
self.e_game_state_dispatcher_event.notify(gameFull=event)
event_topic = gsd_type_to_event_dict.get(event['type'], GSDEventTopics.NOT_IMPLEMENTED)
log.debug(f"GSD Stream event type received: {event['type']} // topic: {event_topic}")

elif event['type'] == "gameState":
if event_topic is EventTopics.MOVE_MADE:
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:
self._game_ended()
self.is_game_over = status and status != "started" and status != "created"

elif event['type'] == "chatLine":
self.e_game_state_dispatcher_event.notify(chatLine=event)

elif event['type'] == "opponentGone":
elif event_topic is GSDEventTopics.OPPONENT_GONE:
is_gone = event.get('gone', False)
secs_until_claim = event.get('claimWinInSeconds', None)

Expand All @@ -54,7 +62,11 @@ def run(self):
if not is_gone:
pass # TODO: Cancel auto-claim countdown

self.e_game_state_dispatcher_event.notify(opponentGone=event)
game_end_event = EventTopics.GAME_END if self.is_game_over else None
self.e_game_state_dispatcher_event.notify(event_topic, game_end_event, data=event)

if self.is_game_over:
self._game_ended()

log.info(f"Completed streaming of: {self.game_id}")

Expand Down Expand Up @@ -94,6 +106,8 @@ def claim_victory(self) -> None:

def _game_ended(self) -> None:
"""Handles removing all event listeners since the game has completed"""
log.info("GAME ENDED: Removing existing GSD listeners")
self.is_game_over = True
self.e_game_state_dispatcher_event.remove_all_listeners()

def subscribe_to_events(self, listener: Callable) -> None:
Expand Down
68 changes: 36 additions & 32 deletions src/cli_chess/core/api/incoming_event_manger.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
from cli_chess.utils.event import Event
from cli_chess.utils.event import Event, EventTopics
from cli_chess.utils.logging import log
from typing import Callable
from enum import Enum, auto
from types import MappingProxyType
import threading


class IEMEventTopics(Enum):
CHALLENGE = auto() # A challenge sent by us or to us
CHALLENGE_CANCELLED = auto()
CHALLENGE_DECLINED = auto()
NOT_IMPLEMENTED = auto()


iem_type_to_event_dict = MappingProxyType({
"gameStart": EventTopics.GAME_START,
"gameFinish": EventTopics.GAME_END,
"challenge": IEMEventTopics.CHALLENGE,
"challengeCanceled": IEMEventTopics.CHALLENGE_CANCELLED,
"challengeDeclined": IEMEventTopics.CHALLENGE_DECLINED,
})


class IncomingEventManager(threading.Thread):
"""Opens a stream and keeps track of Lichess incoming
events (such as game start, game finish).
"""

def __init__(self):
super().__init__(daemon=True)
self.e_new_event_received = Event()
Expand All @@ -22,43 +41,28 @@ def run(self) -> None:
raise ImportError("API client not setup. Do you have an API token linked?")

log.info("Started listening to Lichess incoming events")

for event in api_client.board.stream_incoming_events():
if event['type'] == 'gameStart':
game_id = event['game']['gameId']
log.info(f"Received gameStart for: {game_id}")
self.my_games.append(game_id)
self.e_new_event_received.notify(gameStart=event)

elif event['type'] == 'gameFinish':
game_id = event['game']['gameId']
data = None
event_topic = iem_type_to_event_dict.get(event['type'], IEMEventTopics.NOT_IMPLEMENTED)
log.debug(f"IEM event received: {event}")

if event_topic is EventTopics.GAME_START:
data = event['game']
self.my_games.append(data['gameId'])

elif event_topic is EventTopics.GAME_END:
try:
self.my_games.remove(event['game']['gameId'])
data = event['game']
self.my_games.remove(data['gameId'])
except ValueError:
pass

log.info(f"Received gameEnd for: {game_id}")
self.e_new_event_received.notify(gameFinish=event)

elif event['type'] == 'challenge':
# A challenge was sent by us or to us
challenge_id = event['challenge']['id']
log.info(f"Received challenge event for: {challenge_id}")
self.e_new_event_received.notify(challenge=event)

elif event['type'] == 'challengeCanceled':
challenge_id = event['challenge']['id']
log.info(f"Received challengeCanceled event for: {challenge_id}")
self.e_new_event_received.notify(challengeCanceled=event)

elif event['type'] == 'challengeDeclined':
challenge_id = event['challenge']['id']
log.info(f"Received challengeDeclined event for: {challenge_id}")
self.e_new_event_received.notify(challengeCanceled=event)
elif (event_topic is IEMEventTopics.CHALLENGE or
event_topic is IEMEventTopics.CHALLENGE_CANCELLED or
event_topic is IEMEventTopics.CHALLENGE_DECLINED):
data = event['challenge']

else:
log.info(f"Received other event: {event}")
self.e_new_event_received.notify(other=event)
self.e_new_event_received.notify(event_topic, data=data)

def get_active_games(self) -> list:
"""Returns a list of games in progress for this account"""
Expand Down
Loading

0 comments on commit 5f1a8b1

Please sign in to comment.