From ce76b00d7b2ccac6843732f92becfabb753864a0 Mon Sep 17 00:00:00 2001 From: Eddy Pedroni Date: Thu, 26 Sep 2024 07:43:17 +0200 Subject: Improve documentation --- src/scheduler.py | 41 ++++++++++++++++++++++++++++++++++++----- src/scheduler_brutal.py | 19 ++++++++++++++----- src/session.py | 21 +++++++++++++++++++++ 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/scheduler.py b/src/scheduler.py index 575ec9d..e1c783b 100644 --- a/src/scheduler.py +++ b/src/scheduler.py @@ -3,24 +3,55 @@ from abc import abstractmethod from card import Card class Scheduler(Protocol): + """ + Schedulers must implement this interface to be usable in a session. + """ @abstractmethod - def __init__(self, cards: dict[str, Card], state: dict): raise NotImplementedError + def __init__(self, cards: dict[str, Card], state: dict): + """ + Create a new instance of the scheduler from a dictionary of + Cards indexed by ID and a scheduler-specific state as a dict. + """ + raise NotImplementedError @abstractmethod - def practice(self, size: int) -> list[str]: raise NotImplementedError + def practice(self, size: int) -> list[str]: + """ + Return a list of card IDs of the requested size, if possible. + This list is intended for practice. + """ + raise NotImplementedError @abstractmethod - def test(self, size: int) -> list[str]: raise NotImplementedError + def test(self, size: int) -> list[str]: + """ + Return a list of card IDs of the requested size, if possible. + This list is intended to test the player's knowledge. + """ + raise NotImplementedError @abstractmethod - def update(self, results: dict[str, int]) -> None: raise NotImplementedError + def update(self, results: dict[str, int]) -> None: + """ + Takes a dictionary of card IDs and integers, where the integer + is 0 if the player failed to guess the other side of the card, + of 1 if the player succeeded. + """ + raise NotImplementedError @abstractmethod - def getState(self) -> dict: raise NotImplementedError + def getState(self) -> dict: + """ + Return the scheduler's state for storage. + """ + raise NotImplementedError SCHEDULERS = ["brutal"] def getSchedulerClass(name: str) -> Scheduler: + """ + Returns the class object for the requested scheduler, if one exists. + """ match name: case "brutal": from scheduler_brutal import SchedulerBrutal diff --git a/src/scheduler_brutal.py b/src/scheduler_brutal.py index a476932..f0bfe99 100644 --- a/src/scheduler_brutal.py +++ b/src/scheduler_brutal.py @@ -1,6 +1,3 @@ -""" -""" - from scheduler import Scheduler from card import Card from random import shuffle @@ -8,6 +5,14 @@ from random import shuffle HISTORY_DEPTH = 8 class SchedulerBrutal(Scheduler): + """ + The brutal scheduler tracks how well the player has consolidated each card + and also how often the card has been shown. + + Using this information, it prioritizes cards that have been shown less + frequently and recently, which means the player will often see totally new + cards in test sessions. + """ def __init__(self, cards: dict[str, Card], state: dict): self._cards = cards self._state = {} @@ -38,17 +43,21 @@ class SchedulerBrutal(Scheduler): def getState(self) -> dict: return self._state - # Consolidation index is a measure of how well the card has been memorised @staticmethod def _consolidationIndex(history: list, weights: range) -> float: + """ + Consolidation index is a measure of how well the player has guessed the card recently + """ relevant_history = [(h, w) for h, w in zip(history, weights) if h is not None] weighted_history = sum([h * w for h, w in relevant_history]) total_weights = sum([w for h, w in relevant_history]) return weighted_history / total_weights if total_weights > 0 else 0.0 - # Exposure index is a measure of how much and how recently a card has been shown @staticmethod def _exposureIndex(history: list) -> float: + """ + Exposure index is a measure of how much and how recently a card has been shown + """ return sum([i + 1 for i, h in enumerate(history) if h is not None]) def _schedule(self, size: int) -> list[str]: diff --git a/src/session.py b/src/session.py index 33870aa..f30160f 100644 --- a/src/session.py +++ b/src/session.py @@ -6,17 +6,38 @@ from parser import parseFiles from state_json import load, save class Session: + """ + Represents a play session. During a session, multiple practice and test runs + can be made with the same scheduler. + """ def __init__(self, scheduler_name: str, card_files: list[str], state_file: str): self._cards = parseFiles(card_files) self._state_file = state_file self._scheduler = getSchedulerClass(scheduler_name)(self._cards, load(state_file)) def practice(self, size: int) -> Iterator[Card]: + """ + Yields cards for a practice run of the requested size. + + Practice runs do not affect the scheduler state. + """ ids = self._scheduler.practice(size) for id in ids: yield self._cards[id] def test(self, size: int) -> Iterator[tuple[Card, Callable]]: + """ + Yields cards for a test run of the requested size. + + A function is yielded with each card that takes single boolean argument. + The UI is expected to call the function for each card to indicate whether + the user correctly guessed the card (True) or not (False). + + Multiple subsequent calls to the same function overwrite past results. + + When the test run is done, the scheduler state is updated with the + collected results + """ ids = self._scheduler.practice(size) results = {} -- cgit v1.2.3