change components to inherited boxes, stats are updated by buffer which stores both user and reference text
This commit is contained in:
parent
6cb025c92a
commit
365000701f
8 changed files with 303 additions and 306 deletions
|
@ -3,59 +3,31 @@ import readchar
|
|||
|
||||
|
||||
class Application:
|
||||
def __init__(self, listener, user_buffer, reference_buffer, config):
|
||||
def __init__(self, listener, buffer, config):
|
||||
self.listener = listener
|
||||
self.user_buffer = user_buffer
|
||||
self.reference_buffer = reference_buffer
|
||||
self.buffer = buffer
|
||||
self.config = config
|
||||
self.finished = False
|
||||
self.silent_exit = False
|
||||
|
||||
from .state import StateMachine
|
||||
|
||||
self.state = StateMachine(self)
|
||||
|
||||
from .stats import Stats
|
||||
|
||||
self.stats = Stats()
|
||||
|
||||
def start(self):
|
||||
self.state.signal_start()
|
||||
pass
|
||||
|
||||
def running(self):
|
||||
return self.state.running()
|
||||
|
||||
def valid_user_text_position(self):
|
||||
if self.state.mistake_position is not None:
|
||||
return self.state.mistake_position
|
||||
return self.user_buffer.get_position()
|
||||
|
||||
def user_position(self):
|
||||
return self.user_buffer.get_position()
|
||||
|
||||
def get_text(self):
|
||||
user_text = self.user_buffer.get_matrix()
|
||||
reference_text = self.reference_buffer.get_matrix(
|
||||
self.valid_user_text_position()
|
||||
)
|
||||
return user_text + reference_text
|
||||
return True
|
||||
|
||||
def action(self, screen):
|
||||
try:
|
||||
action, key = self.listener.listen(screen)
|
||||
self.user_buffer.handle_action(action, key)
|
||||
self.state.update(action, key)
|
||||
self.stats.update(action, self.state.valid(), self.state.running())
|
||||
self.buffer.handle_action(action, key)
|
||||
except StoppingSignal as e:
|
||||
self.state.signal_stop()
|
||||
self.stats.signal_stop()
|
||||
if e.silent:
|
||||
self.silent_exit = True
|
||||
|
||||
def summarize(self):
|
||||
if self.finished:
|
||||
self.stats.summarize(self.config.get("summary_template"))
|
||||
self.stats.export_to_datafile(self.config.get("summary_datafile"))
|
||||
self.buffer.stats.summarize(self.config.get("summary_template"))
|
||||
self.buffer.stats.export_to_datafile(self.config.get("summary_datafile"))
|
||||
try:
|
||||
readchar.readchar()
|
||||
except KeyboardInterrupt:
|
||||
|
|
|
@ -1,53 +1,102 @@
|
|||
from .listener import Action
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class CharType(Enum):
|
||||
reference = 0
|
||||
valid = 1
|
||||
invalid = 2
|
||||
|
||||
|
||||
class Buffer:
|
||||
def __init__(self, buffer):
|
||||
self.buffer = " ".join(buffer.split())
|
||||
def __init__(self, buffer, text_box, stats):
|
||||
self.reference_words = buffer.split()
|
||||
self.user_words = []
|
||||
|
||||
def _write(self, data):
|
||||
self.buffer += data
|
||||
self.total_words = len(self.reference_words)
|
||||
self.current_word = 0
|
||||
self.current_char = 0
|
||||
|
||||
self.text_box = text_box
|
||||
self.stats = stats
|
||||
|
||||
self.text_box.set_buffer(self)
|
||||
|
||||
def _write(self, char):
|
||||
while self.current_word >= len(self.user_words):
|
||||
self.user_words.append("")
|
||||
|
||||
valid = False
|
||||
try:
|
||||
valid = (
|
||||
self.reference_words[self.current_word][
|
||||
len(self.user_words[self.current_word])
|
||||
]
|
||||
== char
|
||||
)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if valid:
|
||||
self.stats.signal_valid()
|
||||
else:
|
||||
self.stats.signal_invalid()
|
||||
|
||||
self.user_words[self.current_word] += char
|
||||
self.current_char += 1
|
||||
|
||||
def _next_word(self):
|
||||
self.current_word += 1
|
||||
self.current_char = 0
|
||||
self.stats.signal_valid() # space is a char after all
|
||||
|
||||
def _del_char(self):
|
||||
self.buffer = self.buffer[:-1]
|
||||
try:
|
||||
self.user_words[self.current_word] = self.user_words[self.current_word][:-1]
|
||||
self.current_char -= 1
|
||||
assert self.current_char >= 0
|
||||
except (IndexError, AssertionError):
|
||||
self.user_words = self.user_words[: self.current_word]
|
||||
self.current_word = max(0, self.current_word - 1)
|
||||
|
||||
def _last_char(self):
|
||||
if len(self.buffer) > 0:
|
||||
return self.buffer[-1]
|
||||
if self.current_word < len(self.user_words):
|
||||
self.current_char = len(self.user_words[self.current_word])
|
||||
else:
|
||||
self.current_char = 0
|
||||
|
||||
def _del_word(self):
|
||||
words = self.buffer.split()
|
||||
|
||||
if len(words) > 1:
|
||||
self.buffer = " ".join(words[:-1]) + " "
|
||||
else:
|
||||
self.buffer = ""
|
||||
try:
|
||||
self.user_words[self.current_word] = ""
|
||||
self.current_char = 0
|
||||
except IndexError:
|
||||
self.current_word = max(0, self.current_word - 1)
|
||||
|
||||
def handle_action(self, action, char):
|
||||
if action == Action.add_char:
|
||||
self._write(char)
|
||||
elif action == Action.add_space:
|
||||
self._write(" ")
|
||||
elif action == Action.add_newline:
|
||||
self._write("\n")
|
||||
self._next_word()
|
||||
elif action == Action.del_char:
|
||||
self._del_char()
|
||||
elif action == Action.del_word:
|
||||
self._del_word()
|
||||
|
||||
def get_matrix(self, position=0):
|
||||
return self.buffer[position:]
|
||||
self.text_box.update_current_word(self.current_word)
|
||||
|
||||
def get_position(self):
|
||||
return len(self.buffer)
|
||||
def get_word(self, index):
|
||||
reference_word = self.reference_words[index]
|
||||
user_word = ""
|
||||
|
||||
def get_lenght(self):
|
||||
return len(self.buffer)
|
||||
try:
|
||||
user_word = self.user_words[index]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def read(self, position):
|
||||
return self.buffer[position]
|
||||
word = [
|
||||
(r, CharType.valid if r == u else CharType.invalid)
|
||||
for r, u in zip(reference_word, user_word)
|
||||
]
|
||||
word += [(c, CharType.invalid) for c in user_word[len(word) :]]
|
||||
word += [(c, CharType.reference) for c in reference_word[len(word) :]]
|
||||
|
||||
|
||||
class UserBuffer(Buffer):
|
||||
def __init__(self):
|
||||
super().__init__("")
|
||||
return word
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
from .application import Application
|
||||
from .interface import Interface
|
||||
from .components import (
|
||||
CursorComponent,
|
||||
StatsComponent,
|
||||
TextBox,
|
||||
TopMargin,
|
||||
)
|
||||
from .listener import Listener
|
||||
from .buffer import UserBuffer, Buffer
|
||||
from .buffer import Buffer
|
||||
from .config import Config
|
||||
from .stats import Stats
|
||||
from curses import wrapper
|
||||
import os
|
||||
import argparse
|
||||
|
@ -24,24 +22,18 @@ def initialize(config_path, rbuffer, backspace_debug, no_cursor):
|
|||
|
||||
config = Config(configmap)
|
||||
|
||||
reference_buffer = Buffer(rbuffer)
|
||||
user_buffer = UserBuffer()
|
||||
text_box = TextBox(config)
|
||||
stats = Stats()
|
||||
|
||||
top_margin = TopMargin(config)
|
||||
cursor_component = CursorComponent(config)
|
||||
text_box = TextBox(config, cursor_component)
|
||||
stats_component = StatsComponent(config)
|
||||
buffer = Buffer(rbuffer, text_box, stats)
|
||||
|
||||
listener = Listener(backspace_debug)
|
||||
application = Application(listener, user_buffer, reference_buffer, config)
|
||||
application = Application(listener, buffer, config)
|
||||
|
||||
interface = Interface(
|
||||
application,
|
||||
[
|
||||
top_margin,
|
||||
text_box,
|
||||
stats_component,
|
||||
cursor_component,
|
||||
],
|
||||
no_cursor,
|
||||
)
|
||||
|
@ -58,7 +50,7 @@ def get_parser():
|
|||
"-c",
|
||||
metavar="FILE",
|
||||
help="configuration file",
|
||||
default="~/.config/fasttyper/config.json",
|
||||
default="~/.config/fasttyper/config_debug.json",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--unclutter-backspace",
|
||||
|
|
|
@ -1,191 +1,236 @@
|
|||
import curses
|
||||
|
||||
|
||||
class Base:
|
||||
class WindowComponent:
|
||||
"""
|
||||
Basic text component printing inside window
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self._window = None
|
||||
self._height = None
|
||||
self._width = None
|
||||
self._begin_x = None
|
||||
self._begin_y = None
|
||||
|
||||
self.cursor_x, self.cursor_y = 0, 0
|
||||
|
||||
def update_size(self, height, width, begin_x, begin_y):
|
||||
self._height = height
|
||||
self._width = width
|
||||
self._begin_x = begin_x
|
||||
self._begin_y = begin_y
|
||||
|
||||
def init_window(self):
|
||||
self._window = curses.newwin(
|
||||
self._height,
|
||||
self._width,
|
||||
self._begin_y,
|
||||
self._begin_x,
|
||||
)
|
||||
|
||||
def set_box(self, i):
|
||||
self._window.box(i, i)
|
||||
|
||||
def paint_text(self, row, col, text, color):
|
||||
self._window.addstr(row, col, text, curses.color_pair(color))
|
||||
|
||||
def move(self, x, y):
|
||||
self._window.move(x, y)
|
||||
|
||||
def paint(self, screen, application):
|
||||
pass
|
||||
|
||||
def refresh(self):
|
||||
self._window.refresh()
|
||||
|
||||
|
||||
class BorderedBox(WindowComponent):
|
||||
"""
|
||||
Adds border to WindowComponent
|
||||
"""
|
||||
|
||||
class CursorComponent(Base):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.cursor_position = None
|
||||
super().__init__(config)
|
||||
|
||||
def update(self, screen):
|
||||
self.cursor_position = screen.getyx()
|
||||
self.maxy, self.maxx = None, None
|
||||
|
||||
def paint(self, screen, application):
|
||||
if self.cursor_position:
|
||||
screen.move(*self.cursor_position)
|
||||
self.pos_y = config.get("top_margin_percentage") / 100
|
||||
self.pos_x = config.get("left_margin_percentage") / 100
|
||||
self.height = config.get("lines_on_screen")
|
||||
self.width = None
|
||||
|
||||
def paint_text(self, row, col, text, color):
|
||||
super().paint_text(row + 1, col + 1, text, color)
|
||||
|
||||
class TextComponent(Base):
|
||||
def paint_text(self, screen, text, color):
|
||||
screen.addstr(text, curses.color_pair(color))
|
||||
|
||||
|
||||
class StatsComponent(TextComponent):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.color = config.get("stats_color")
|
||||
self.template = config.get("stats_template")
|
||||
def move(self, x, y):
|
||||
super().move(x + 1, y + 1)
|
||||
|
||||
def init(self, screen, application):
|
||||
super().init(screen, application)
|
||||
self.width = int(self.maxx * (1 - 2 * self.pos_x))
|
||||
self.update_size(
|
||||
self.height + 2,
|
||||
self.width + 2,
|
||||
int(self.pos_x * self.maxx) - 1,
|
||||
int(self.pos_y * self.maxy) - 1,
|
||||
)
|
||||
self.init_window()
|
||||
self.set_box(1)
|
||||
|
||||
def paint(self, screen, application):
|
||||
usedy, _ = screen.getyx()
|
||||
maxy, _ = screen.getmaxyx()
|
||||
|
||||
text = self.template.format(stats=application.stats)
|
||||
|
||||
texty = len(text.splitlines())
|
||||
|
||||
if texty + usedy + 1 < maxy:
|
||||
self.paint_text(screen, text, self.color)
|
||||
screen.refresh()
|
||||
|
||||
|
||||
class TopMargin(Base):
|
||||
class BufferDependentComponent(BorderedBox):
|
||||
"""
|
||||
Adds content source from buffer
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.height = config.get("top_margin_percentage") / 100
|
||||
super().__init__(config)
|
||||
|
||||
def paint(self, screen, application):
|
||||
maxy, _ = screen.getmaxyx()
|
||||
lines = int(self.height * maxy)
|
||||
for line in range(lines):
|
||||
screen.addstr("\n")
|
||||
self.buffered_lines = 0
|
||||
self.buffer = None
|
||||
self.last_hidden_word = 0
|
||||
self.lines = [[] for _ in range(self.height)]
|
||||
|
||||
from .buffer import CharType
|
||||
|
||||
self.chtype_mapper = {
|
||||
CharType.valid: config.get("user_input_valid_color"),
|
||||
CharType.invalid: config.get("user_input_invalid_color"),
|
||||
CharType.reference: config.get("reference_text_color"),
|
||||
}
|
||||
|
||||
self.current_word_idx = 0
|
||||
self.current_line, self.word_index = 0, 0
|
||||
|
||||
def set_buffer(self, buffer):
|
||||
self.buffer = buffer
|
||||
|
||||
def line_len(self, index):
|
||||
spaces = max(len(self.lines[index]) - 1, 0)
|
||||
return sum([len(w) for w in self.lines[index]]) + spaces
|
||||
|
||||
def paint_line(self, line_nr):
|
||||
pos = 0
|
||||
|
||||
for i, word in enumerate(self.lines[line_nr]):
|
||||
for c in word:
|
||||
self.paint_text(line_nr, pos, c[0], self.chtype_mapper[c[1]])
|
||||
pos += 1
|
||||
|
||||
if i != len(self.lines[line_nr]):
|
||||
self.paint_text(line_nr, pos, " ", 0)
|
||||
pos += 1
|
||||
|
||||
def fill_lines(self):
|
||||
line_nr = self.buffered_lines
|
||||
start_idx = self.last_hidden_word + sum(
|
||||
[len(line) for line in self.lines[:line_nr]]
|
||||
)
|
||||
|
||||
for i in range(start_idx, self.buffer.total_words):
|
||||
word = self.buffer.get_word(i)
|
||||
|
||||
if self.line_len(line_nr) + len(word) + 1 > self.width:
|
||||
self.paint_line(line_nr)
|
||||
line_nr += 1
|
||||
|
||||
if line_nr >= self.height:
|
||||
break
|
||||
|
||||
self.lines[line_nr].append(word)
|
||||
|
||||
self.paint_line(line_nr)
|
||||
self.buffered_lines = self.height
|
||||
|
||||
def update_cursor(self):
|
||||
self.cursor_x = self.current_line
|
||||
past_words = self.lines[self.current_line][: self.word_index]
|
||||
self.cursor_y = (
|
||||
sum([len(w) for w in past_words])
|
||||
+ len(past_words)
|
||||
+ self.buffer.current_char
|
||||
)
|
||||
|
||||
def update_current_word(self, word_index):
|
||||
"""
|
||||
This is called by buffer. It signals that user changed its state.
|
||||
|
||||
First, active word is updated. It changes on added char or deleted word.
|
||||
"""
|
||||
|
||||
old_len = len(self.lines[self.current_line][self.word_index])
|
||||
self.lines[self.current_line][self.word_index] = self.buffer.get_word(
|
||||
self.current_word_idx
|
||||
)
|
||||
new_len = len(self.lines[self.current_line][self.word_index])
|
||||
|
||||
if old_len != new_len:
|
||||
pass
|
||||
|
||||
while word_index > self.current_word_idx:
|
||||
self.word_index += 1
|
||||
|
||||
if self.word_index >= len(self.lines[self.current_line]):
|
||||
self.word_index = 0
|
||||
self.current_line += 1
|
||||
|
||||
self.current_word_idx += 1
|
||||
self.lines[self.current_line][self.word_index] = self.buffer.get_word(
|
||||
self.current_word_idx
|
||||
)
|
||||
|
||||
while word_index < self.current_word_idx:
|
||||
self.word_index -= 1
|
||||
|
||||
if self.word_index == -1:
|
||||
self.current_line -= 1
|
||||
self.word_index = len(self.lines[self.current_line] - 1)
|
||||
|
||||
self.current_word_idx -= 1
|
||||
self.lines[self.current_line][self.word_index] = self.buffer.get_word(
|
||||
self.current_word_idx
|
||||
)
|
||||
|
||||
self.update_cursor()
|
||||
|
||||
|
||||
class TextBox(TextComponent):
|
||||
class BorderWithImprintedStats(BufferDependentComponent):
|
||||
"""
|
||||
Wraps lines of text elements writing to it nicely
|
||||
Imprints stats on one of borders
|
||||
"""
|
||||
|
||||
def __init__(self, config, cursor_component):
|
||||
super().__init__()
|
||||
self.cursor_component = cursor_component
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
|
||||
self.maxy, self.maxx = None, None
|
||||
self.usedy, self.usedx = None, None
|
||||
self.stats_template = config.get("stats_template").replace("\n", " ")
|
||||
self.stats_color = config.get("stats_color")
|
||||
self.stats_row = -1 if config.get("stats_position") == "top" else self.height
|
||||
|
||||
self.left_margin = config.get("left_margin_percentage")
|
||||
def paint_stats(self):
|
||||
text = self.stats_template.format(stats=self.buffer.stats)
|
||||
if len(text) < self.width - 2:
|
||||
self.paint_text(self.stats_row, 2, text, self.stats_color)
|
||||
|
||||
self.valid_color = config.get("user_input_valid_color")
|
||||
self.invalid_color = config.get("user_input_invalid_color")
|
||||
self.color = config.get("reference_text_color")
|
||||
|
||||
self.lines_on_screen = config.get("lines_on_screen")
|
||||
class TextBox(BorderWithImprintedStats):
|
||||
"""
|
||||
Calls all inherited paint functions and inits windows
|
||||
"""
|
||||
|
||||
def clear(self):
|
||||
self.maxy, self.maxx = None, None
|
||||
self.usedy, self.usedx = None, None
|
||||
|
||||
@property
|
||||
def max_line_x(self):
|
||||
return int(self.maxx * (100 - self.left_margin - self.left_margin) / 100) - 1
|
||||
|
||||
@property
|
||||
def padding(self):
|
||||
return " " * int(1 + self.left_margin * self.maxx / 100)
|
||||
|
||||
def lines_to_display(self, application):
|
||||
text = application.get_text()
|
||||
valid_position = application.valid_user_text_position()
|
||||
user_position = application.user_position()
|
||||
|
||||
lines = []
|
||||
line = ""
|
||||
word = ""
|
||||
|
||||
valid_pointer = (0, 0)
|
||||
user_pointer = (0, 0)
|
||||
|
||||
for i, c in enumerate(text):
|
||||
word += c
|
||||
|
||||
if len(line + word) > self.max_line_x:
|
||||
lines.append(line)
|
||||
line = ""
|
||||
|
||||
if not c.isalnum():
|
||||
word = str(word)[:-1] + " "
|
||||
line += word
|
||||
word = ""
|
||||
|
||||
if word:
|
||||
line += word
|
||||
|
||||
if line:
|
||||
lines.append(line)
|
||||
|
||||
position = 0
|
||||
for i, l in enumerate(lines):
|
||||
st, end = position, position + len(l)
|
||||
if user_position >= st and user_position <= end:
|
||||
user_pointer = (i, user_position - st)
|
||||
if valid_position >= st and valid_position <= end:
|
||||
valid_pointer = (i, valid_position - st)
|
||||
position = end
|
||||
|
||||
return lines, valid_pointer, user_pointer
|
||||
|
||||
def paint_line(self, i, line, valid_pointer, user_pointer, screen):
|
||||
if i < valid_pointer[0]:
|
||||
# easy, we are in written line
|
||||
self.paint_text(screen, line, self.valid_color)
|
||||
self.cursor_component.update(screen)
|
||||
return
|
||||
|
||||
if i > user_pointer[0]:
|
||||
# easy, we are in reference line
|
||||
self.paint_text(screen, line, self.color)
|
||||
return
|
||||
|
||||
valid_text = ""
|
||||
invalid_text = ""
|
||||
invalid_start = 0
|
||||
|
||||
if i == valid_pointer[0]:
|
||||
valid_text = line[: valid_pointer[1]]
|
||||
invalid_start = valid_pointer[1]
|
||||
invalid_text = line[valid_pointer[1] :]
|
||||
if i == user_pointer[0]:
|
||||
invalid_text = line[invalid_start : user_pointer[1]]
|
||||
|
||||
reference_text = line[len(invalid_text) + len(valid_text) :]
|
||||
invalid_text = invalid_text.replace(" ", "_")
|
||||
|
||||
self.paint_text(screen, valid_text, self.valid_color)
|
||||
self.paint_text(screen, invalid_text, self.invalid_color)
|
||||
self.cursor_component.update(screen)
|
||||
self.paint_text(screen, reference_text, self.color)
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
|
||||
def paint(self, screen, application):
|
||||
self.maxy, self.maxx = screen.getmaxyx()
|
||||
self.usedy, self.usedx = screen.getyx()
|
||||
if self._window is None:
|
||||
self.maxy, self.maxx = screen.getmaxyx()
|
||||
self.init(screen, application)
|
||||
|
||||
lines, valid_pointer, user_pointer = self.lines_to_display(application)
|
||||
if self.buffered_lines < self.height:
|
||||
self.fill_lines()
|
||||
|
||||
lines_fitting = min((self.maxy - self.usedy, self.lines_on_screen))
|
||||
start = 0
|
||||
end = len(lines)
|
||||
|
||||
if lines_fitting == 0:
|
||||
raise Exception("Too small display!")
|
||||
|
||||
if lines_fitting <= len(lines):
|
||||
previous_lines = 1 if lines_fitting > 2 else 0
|
||||
next_lines = 1 if lines_fitting > 1 else 0
|
||||
start = user_pointer[0] - previous_lines
|
||||
end = user_pointer[0] + next_lines
|
||||
if start == -1:
|
||||
end += 1
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if i >= start and i <= end:
|
||||
screen.addstr(self.padding)
|
||||
self.paint_line(i, line, valid_pointer, user_pointer, screen)
|
||||
screen.addstr("\n")
|
||||
|
||||
self.clear()
|
||||
self.paint_stats()
|
||||
self.paint_line(self.current_line)
|
||||
self.move(self.cursor_x, self.cursor_y)
|
||||
self.refresh()
|
||||
|
|
|
@ -3,8 +3,9 @@ class Config:
|
|||
"user_input_valid_color": 3,
|
||||
"user_input_invalid_color": 2,
|
||||
"reference_text_color": 8,
|
||||
"stats_template": "\n\nwpm: {stats.wpm:0.2f}\ntime: {stats.total_seconds:0.2f}s",
|
||||
"stats_template": "wpm: {stats.wpm:0.2f}, time: {stats.total_seconds:0.2f}s",
|
||||
"stats_color": 5,
|
||||
"stats_position": "top",
|
||||
"summary_template": (
|
||||
"WPM: {stats.wpm:0.2f}\n"
|
||||
"CPM: {stats.cpm:0.2f}\n"
|
||||
|
|
|
@ -19,10 +19,8 @@ class Interface:
|
|||
self.init_colors()
|
||||
|
||||
def update(self, screen):
|
||||
screen.clear()
|
||||
for component in self.components:
|
||||
component.paint(screen, self.application)
|
||||
screen.refresh()
|
||||
|
||||
def __call__(self, screen):
|
||||
"""
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
import enum
|
||||
from .listener import Action
|
||||
from .application import StoppingSignal
|
||||
|
||||
|
||||
class State(enum.Enum):
|
||||
valid = "valid"
|
||||
invalid = "invalid"
|
||||
finished = "finished"
|
||||
|
||||
|
||||
class StateMachine:
|
||||
def __init__(self, application):
|
||||
self.state = State.valid
|
||||
self.mistake_position = None
|
||||
self.application = application
|
||||
|
||||
def running(self):
|
||||
return self.state != State.finished
|
||||
|
||||
def signal_stop(self):
|
||||
self.state = State.finished
|
||||
|
||||
def signal_start(self):
|
||||
self.state = State.valid
|
||||
self.mistake_position = None
|
||||
|
||||
def update(self, action, char):
|
||||
user_position = self.application.user_buffer.get_position()
|
||||
if self.state == State.invalid:
|
||||
if action in (Action.del_char, Action.del_word):
|
||||
if user_position <= self.mistake_position:
|
||||
self.mistake_position = None
|
||||
self.state = State.valid
|
||||
if self.state == State.valid:
|
||||
if action in (Action.add_char, Action.add_space, Action.add_newline):
|
||||
if char != self.application.reference_buffer.read(user_position - 1):
|
||||
self.mistake_position = user_position - 1
|
||||
self.state = State.invalid
|
||||
elif user_position == self.application.reference_buffer.get_lenght():
|
||||
self.state = State.finished
|
||||
self.application.finished = True
|
||||
|
||||
def valid(self):
|
||||
return self.state in (State.valid, State.finished)
|
|
@ -19,28 +19,13 @@ class Stats:
|
|||
if self.start_dtime is None:
|
||||
self.start_dtime = datetime.now()
|
||||
|
||||
def update(self, action, valid, running):
|
||||
if running:
|
||||
self.signal_running()
|
||||
def signal_valid(self):
|
||||
self.signal_running()
|
||||
self.correct_chars += 1
|
||||
|
||||
if action == Action.add_char and valid is True:
|
||||
self.correct_chars += 1
|
||||
elif action == Action.add_char and valid is False:
|
||||
self.incorrect_chars += 1
|
||||
elif action in (Action.add_space, Action.add_newline) and valid is True:
|
||||
self.correct_chars += 1
|
||||
self.correct_words += 1
|
||||
elif action in (Action.add_space, Action.add_newline) and valid is False:
|
||||
self.incorrect_chars += 1
|
||||
self.incorrect_words += 1
|
||||
|
||||
if not running:
|
||||
self.signal_stop()
|
||||
|
||||
if valid:
|
||||
self.correct_words += 1
|
||||
else:
|
||||
self.incorrect_words += 1
|
||||
def signal_invalid(self):
|
||||
self.signal_running()
|
||||
self.incorrect_chars += 1
|
||||
|
||||
def signal_stop(self):
|
||||
self.stop_dtime = datetime.now()
|
||||
|
|
Loading…
Reference in a new issue