summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/scheduler.py41
-rw-r--r--src/scheduler_brutal.py19
-rw-r--r--src/session.py21
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 = {}