Skip to content

Commit

Permalink
Add an option for 2-answer scheme.
Browse files Browse the repository at this point in the history
  • Loading branch information
porridge committed Jul 12, 2022
1 parent a1b4a8b commit 1465e6c
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 37 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ An application for learning multiplication table.
### Basic Operation

The application selects a question from a 10x10 multiplication table and displays it in the middle of the screen.
It also displays four answers to choose from: below, above and on both sides of the question.
It also displays possible answers to choose from, next to the question.
Only one of the answers is correct.

The user needs to select the correct answer by pressing a key corresponding to the position of the answer.
Expand Down Expand Up @@ -52,6 +52,15 @@ They are displayed in the lower corners of the main window.
- Pass the `--show-scores` option to show scores.
- Use the`--score-font` option to select the font to use for displaying scores.

### Choosing from two or four possible answers

By default the application displays four possible answers.
They are shown below, above and on both sides of the question.
- Pass the `--answer-scheme=EW` option to show only two possible answers.
- Pass the `--answer-scheme=NESW` option to show four possible answers.

In either mode, only one of the displayed answers is the correct one.

### Limiting the Number of Questions

By default the application will keep asking questions until it is closed.
Expand Down
128 changes: 92 additions & 36 deletions tabliczka.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@
_FREQ_QUICK = 1
_ANSWER_SEC_QUICK= 2
_DEFAULT_SCORE_FONT = 'monospace'
_DEFAULT_ANSWER_SCHEME = 'NESW'

_KEYS_ARROWS = (pygame.K_UP, pygame.K_RIGHT, pygame.K_DOWN, pygame.K_LEFT)
# The order of the following matches the order of the above.
_KEYS_MINECRAFT_LOWER = 'wdsa'
_KEYS_MINECRAFT_UPPER = 'WDSA'
_KEYS_ARROWS = (pygame.K_UP, pygame.K_RIGHT, pygame.K_DOWN, pygame.K_LEFT)

_home = os.path.expanduser('~')
_xdg_state_home = os.environ.get('XDG_STATE_HOME') or os.path.join(_home, '.local', 'state')
Expand All @@ -67,6 +69,7 @@ def get_argument_parser():
parser.add_argument('--show-feedback', action=argparse.BooleanOptionalAction, help='Show feedback on wrong answers.')
parser.add_argument('--show-scores', action=argparse.BooleanOptionalAction, help='Show scores in main window.')
parser.add_argument('--score-font', help='Font to use for displaying scores (defaults to %s).' % _DEFAULT_SCORE_FONT)
parser.add_argument('--answer-scheme', choices=[_DEFAULT_ANSWER_SCHEME, 'EW'], default=None, help='Where to show possible answers (letters stand for geographic directions relative to displayed question).')

return parser

Expand Down Expand Up @@ -121,7 +124,7 @@ class Settings:

def __init__(self, fs, parsed_args):
self._s = dict((k, None) for k in [
'limit', 'show_scores', 'show_feedback', 'score_font'])
'limit', 'show_scores', 'show_feedback', 'score_font', 'answer_scheme'])
self._load_settings(fs)
self._merge_settings(parsed_args)
self._save_settings(fs)
Expand All @@ -142,6 +145,10 @@ def show_feedback(self):
def score_font(self):
return self._s['score_font'] or _DEFAULT_SCORE_FONT

@property
def answer_scheme(self):
return self._s['answer_scheme'] or _DEFAULT_ANSWER_SCHEME

def _load_settings(self, fs):
loaded = fs.read()
if not loaded:
Expand Down Expand Up @@ -299,6 +306,7 @@ def __init__(self, settings):
self._should_show_scores = settings.show_scores
self._should_show_feedback = settings.show_feedback
self._score_font_name = settings.score_font
self._answer_scheme = settings.answer_scheme

def __enter__(self):
logging.debug('Initializing pygame.')
Expand Down Expand Up @@ -327,10 +335,10 @@ def _tick(self):
self._clock.tick(30) # low framerate is fine for this app

def answer_count(self):
return 4
return len(self._answer_scheme)

def solve_problem(self, problem, state):
answers = self._display_problem(problem, state)
answer_map = self._display_problem(problem, state)

asked_time = time.time()

Expand All @@ -342,16 +350,12 @@ def solve_problem(self, problem, state):
logging.debug('Initiating shutdown.')
raise QuitException()
if event.type == pygame.KEYDOWN:
if event.unicode and event.unicode in _KEYS_MINECRAFT_LOWER:
answer_index = _KEYS_MINECRAFT_LOWER.index(event.unicode)
elif event.unicode and event.unicode in _KEYS_MINECRAFT_UPPER:
answer_index = _KEYS_MINECRAFT_UPPER.index(event.unicode)
elif event.key in _KEYS_ARROWS:
answer_index = _KEYS_ARROWS.index(event.key)
logging.debug(answer_map)
if answer_map.has_answer_for(event):
problem.answered(answer_map.answer_for(event), asked_time)
return
else:
continue
problem.answered(answers[answer_index], asked_time)
return

def provide_feedback(self, problem, state):
if not self._should_show_feedback:
Expand All @@ -374,12 +378,11 @@ def _display_problem(self, problem, state, reveal_solution=False):
self._show_correct_score(state)
self._show_error_score(state)
self._show_question(problem)
answers = problem.answers()
self._show_answers(problem, answers, reveal_solution=reveal_solution)
answer_map = self._show_answers(problem, problem.answers(), reveal_solution=reveal_solution)
logging.debug('Updating display.')
pygame.display.flip()
logging.debug('Problem displayed.')
return answers
return answer_map

def _show_correct_score(self, state):
screen_bottom_left = self._screen.get_rect().bottomleft
Expand Down Expand Up @@ -412,26 +415,42 @@ def _show_question(self, problem):

def _show_answers(self, problem, answers, reveal_solution=False):
screen_center = self._screen.get_rect().center

answer_up = self._font.render(answers[0], 1, self._text_color)
answer_up_rect = answer_up.get_rect(center=(screen_center[0], int(1.5*self._digit_size[1])))
pygame.draw.rect(self._screen, self._answer_color(problem, answers[0], reveal_solution), answer_up_rect)
self._screen.blit(answer_up, answer_up_rect)

answer_right = self._font.render(answers[1], 1, self._text_color)
answer_right_rect = answer_right.get_rect(center=(int(21*self._digit_size[0]), screen_center[1]))
pygame.draw.rect(self._screen, self._answer_color(problem, answers[1], reveal_solution), answer_right_rect)
self._screen.blit(answer_right, answer_right_rect)

answer_down = self._font.render(answers[2], 1, self._text_color)
answer_down_rect = answer_down.get_rect(center=(screen_center[0], int(5.5*self._digit_size[1])))
pygame.draw.rect(self._screen, self._answer_color(problem, answers[2], reveal_solution), answer_down_rect)
self._screen.blit(answer_down, answer_down_rect)

answer_left = self._font.render(answers[3], 1, self._text_color)
answer_left_rect = answer_left.get_rect(center=(int(3*self._digit_size[0]), screen_center[1]))
pygame.draw.rect(self._screen, self._answer_color(problem, answers[3], reveal_solution), answer_left_rect)
self._screen.blit(answer_left, answer_left_rect)
answers = list(answers) # copy before mutating the list
answer_map = AnswerMap()

if 'N' in self._answer_scheme:
answer_up = answers.pop(0)
answer_up_surface = self._font.render(answer_up, 1, self._text_color)
answer_up_rect = answer_up_surface.get_rect(center=(screen_center[0], int(1.5*self._digit_size[1])))
pygame.draw.rect(self._screen, self._answer_color(problem, answer_up, reveal_solution), answer_up_rect)
self._screen.blit(answer_up_surface, answer_up_rect)
answer_map.answer_up(answer_up)

if 'E' in self._answer_scheme:
answer_right = answers.pop(0)
answer_right_surface = self._font.render(answer_right, 1, self._text_color)
answer_right_rect = answer_right_surface.get_rect(center=(int(21*self._digit_size[0]), screen_center[1]))
pygame.draw.rect(self._screen, self._answer_color(problem, answer_right, reveal_solution), answer_right_rect)
self._screen.blit(answer_right_surface, answer_right_rect)
answer_map.answer_right(answer_right)

if 'S' in self._answer_scheme:
answer_down = answers.pop(0)
answer_down_surface = self._font.render(answer_down, 1, self._text_color)
answer_down_rect = answer_down_surface.get_rect(center=(screen_center[0], int(5.5*self._digit_size[1])))
pygame.draw.rect(self._screen, self._answer_color(problem, answer_down, reveal_solution), answer_down_rect)
self._screen.blit(answer_down_surface, answer_down_rect)
answer_map.answer_down(answer_down)

if 'W' in self._answer_scheme:
answer_left = answers.pop(0)
answer_left_surface = self._font.render(answer_left, 1, self._text_color)
answer_left_rect = answer_left_surface.get_rect(center=(int(3*self._digit_size[0]), screen_center[1]))
pygame.draw.rect(self._screen, self._answer_color(problem, answer_left, reveal_solution), answer_left_rect)
self._screen.blit(answer_left_surface, answer_left_rect)
answer_map.answer_left(answer_left)

return answer_map


def _answer_color(self, problem, answer, reveal_solution=False):
Expand All @@ -440,8 +459,45 @@ def _answer_color(self, problem, answer, reveal_solution=False):
return self._answer_correct_color if problem.correct_answer() == answer else self._answer_error_color


class AnswerMap:

def __init__(self):
self._answers = dict(
up=None,
right=None,
down=None,
left=None)

def answer_up(self, answer):
self._answers['up'] = answer

def answer_right(self, answer):
self._answers['right'] = answer

def answer_down(self, answer):
self._answers['down'] = answer

def answer_left(self, answer):
self._answers['left'] = answer

def has_answer_for(self, event):
return self.answer_for(event) != None

def answer_for(self, event):
if event.unicode and event.unicode in _KEYS_MINECRAFT_LOWER:
answer_index = _KEYS_MINECRAFT_LOWER.index(event.unicode)
elif event.unicode and event.unicode in _KEYS_MINECRAFT_UPPER:
answer_index = _KEYS_MINECRAFT_UPPER.index(event.unicode)
elif event.key and event.key in _KEYS_ARROWS:
answer_index = _KEYS_ARROWS.index(event.key)
else:
return None
direction = ['up', 'right', 'down', 'left'][answer_index]
return self._answers[direction]


class Problem:

def __init__(self, a, b, answer_count):
self._a = a
self._b = b
Expand Down

0 comments on commit 1465e6c

Please sign in to comment.