From 411fddce31bf7b7a903400c060a2a99819cc3fda Mon Sep 17 00:00:00 2001 From: sergiotarxz Date: Sat, 13 Jul 2024 02:33:00 +0200 Subject: [PATCH] Adding mode for dyslexia. --- lib/JapaChar.pm | 18 ++++- lib/JapaChar/Characters.pm | 67 ++++++++++++++----- lib/JapaChar/DB/Migrations.pm | 1 + lib/JapaChar/Schema/Result/BasicCharacter.pm | 36 ++++++---- .../View/HiraganaKatakanaTestExercise.pm | 25 +++++-- lib/JapaChar/View/MainMenu.pm | 9 +++ 6 files changed, 117 insertions(+), 39 deletions(-) diff --git a/lib/JapaChar.pm b/lib/JapaChar.pm index 5fc9231..81e2459 100644 --- a/lib/JapaChar.pm +++ b/lib/JapaChar.pm @@ -49,9 +49,21 @@ Glib::Object::Introspection->setup( has headerbar => ( is => 'rw', ); has _on_resize_lesson => ( is => 'rw', ); -has _gresources_path => ( is => 'lazy', ); -has _window => ( is => 'rw' ); -has _on_resize_triggers => ( is => 'ro', default => sub { {}; } ); +has _gresources_path => ( is => 'lazy', ); +has _window => ( is => 'rw' ); +has _on_resize_triggers => ( is => 'ro', default => sub { {}; } ); +has accessibility => ( is => 'lazy' ); +has characters => ( is => 'lazy' ); + +sub _build_characters($self) { + require JapaChar::Characters; + return JapaChar::Characters->new; +} + +sub _build_accessibility($self) { + require JapaChar::Accessibility; + return JapaChar::Accessibility->new(app => $self); +} sub _build__gresources_path($self) { my $root = path(__FILE__)->parent->parent; diff --git a/lib/JapaChar/Characters.pm b/lib/JapaChar/Characters.pm index 14ed807..9bf4aaa 100644 --- a/lib/JapaChar/Characters.pm +++ b/lib/JapaChar/Characters.pm @@ -16,6 +16,9 @@ my $option_populated = 'populated_basic_characters'; require JapaChar::DB; require JapaChar::Schema; +has is_repeated => ( is => 'rw', default => sub { 0 } ); +has _times_repeated => ( is => 'rw', default => sub { 0 }); + sub populate_basic_characters($self) { my $dbh = JapaChar::DB->connect; my $result = $dbh->selectrow_hashref( @@ -40,11 +43,10 @@ sub _populate_type( $self, $type ) { my $kana = $char->{kana}; my $romanji = $char->{roumaji}; next if $romanji =~ /pause/i; - push @array_for_insertion, { value => $kana, romanji => $romanji, type => $type }; + push @array_for_insertion, + { value => $kana, romanji => $romanji, type => $type }; } - $basic_character_resultset->populate([ - @array_for_insertion - ]); + $basic_character_resultset->populate( [@array_for_insertion] ); } sub _get_characters_of_type( $self, $type ) { @@ -60,7 +62,7 @@ sub get_4_incorrect_answers( $self, $char ) { JapaChar::Schema->Schema->resultset('BasicCharacter'); my @bad_answers = $basic_character_resultset->search( { - type => $char->type, + type => $char->type, value => { '!=', $char->value }, romanji => { '!=', $char->romanji }, -bool => 'started', @@ -79,9 +81,7 @@ sub _next_review_char( $self, $type = undef ) { my @chars = $basic_character_resultset->search( { score => { '>=' => 100 }, - ( - ( defined $type ) ? ( type => $type, ) : () - ) + ( ( defined $type ) ? ( type => $type, ) : () ) }, { order_by => { -asc => \'RANDOM()' }, @@ -94,22 +94,59 @@ sub _next_review_char( $self, $type = undef ) { return $chars[0]; } -sub next_char( $self, $type = undef ) { +sub _try_next_char_dyslexia($self, $type = undef) { + my $next_repeated_character = $self->_next_repeated_character($type); + if ( !defined $next_repeated_character ) { + return; + } + $self->is_repeated(1); + $self->_times_repeated($self->_times_repeated + 1); + if ($self->_times_repeated > 4) { + $self->is_repeated(0); + $self->_times_repeated(0); + return; + } + return $next_repeated_character; +} + +sub last_repeated($self) { + return $self->_times_repeated >= 4; +} + +sub next_char( $self, $accesibility, $type = undef ) { + if ( $accesibility->is_dyslexia ) { + my $dyslexia_char = $self->_try_next_char_dyslexia; + return $dyslexia_char if defined $dyslexia_char; + } + $self->is_repeated(0); my $next_review = $self->_next_review_char($type); my $next_learning = $self->_next_learning_char($type); if ( !defined $next_review ) { return $next_learning; } - if ( !defined $next_learning) { + if ( !defined $next_learning ) { return $next_review; } - my $rng = JapaChar::Random->new->get(1, 100); + my $rng = JapaChar::Random->new->get( 1, 100 ); if ( $rng > 20 ) { return $next_learning; } return $next_review; } +sub _next_repeated_character( $self, $type = undef ) { + my $basic_character_resultset = + JapaChar::Schema->Schema->resultset('BasicCharacter'); + my ($char) = $basic_character_resultset->search( + { + consecutive_failures => { '>=' => 3 }, + }, + { + order_by => { -asc => 'id' }, + } + ); + return $char; +} sub _next_learning_char( $self, $type = undef ) { $self->populate_basic_characters; @@ -120,9 +157,7 @@ sub _next_learning_char( $self, $type = undef ) { my @new_chars = $basic_character_resultset->search( { -not_bool => 'started', - ( - ( defined $type ) ? ( type => $type, ) : () - ) + ( ( defined $type ) ? ( type => $type, ) : () ) }, { order_by => { -asc => 'id' }, @@ -143,9 +178,7 @@ sub _retrieve_started_chars_not_finished( $self, $type ) { JapaChar::Schema->Schema->resultset('BasicCharacter'); return $basic_character_resultset->search( { - ( - ( defined $type ) ? ( type => $type, ) : () - ), + ( ( defined $type ) ? ( type => $type, ) : () ), score => { '<' => 100 }, -bool => 'started', } diff --git a/lib/JapaChar/DB/Migrations.pm b/lib/JapaChar/DB/Migrations.pm index 001d79e..2d83cdc 100644 --- a/lib/JapaChar/DB/Migrations.pm +++ b/lib/JapaChar/DB/Migrations.pm @@ -24,6 +24,7 @@ sub MIGRATIONS { consecutive_success INTEGER NOT NULL DEFAULT 0 );', 'INSERT INTO options (name, value) VALUES (\'user_score\', \'0\');', + 'ALTER TABLE basic_characters ADD consecutive_failures INTEGER NOT NULL DEFAULT 0', ); } 1; diff --git a/lib/JapaChar/Schema/Result/BasicCharacter.pm b/lib/JapaChar/Schema/Result/BasicCharacter.pm index 81f1fb1..fd7967b 100644 --- a/lib/JapaChar/Schema/Result/BasicCharacter.pm +++ b/lib/JapaChar/Schema/Result/BasicCharacter.pm @@ -21,7 +21,7 @@ __PACKAGE__->add_columns( value => { data_type => 'TEXT', is_nullable => 0, - accessor => '_value', + accessor => '_value', }, romanji => { data_type => 'TEXT', @@ -43,10 +43,14 @@ __PACKAGE__->add_columns( data_type => 'INTEGER', is_nullable => 1, }, + consecutive_failures => { + data_type => 'INTEGER', + is_nullable => 1, + }, ); -sub value($self, $value = undef) { - if (defined $value) { +sub value( $self, $value = undef ) { + if ( defined $value ) { $self->_value($value); } return decode 'utf-8', $self->_value; @@ -55,41 +59,45 @@ sub value($self, $value = undef) { __PACKAGE__->set_primary_key('id'); sub fail($self) { - my $score = $self->score; - my $consecutive_success = 0; + my $score = $self->score; + my $consecutive_success = 0; + my $consecutive_failures = $self->consecutive_failures + 1; $score -= 25; if ( $score < 0 ) { $score = 0; } $self->update( { - score => $score, - consecutive_success => 0 + score => $score, + consecutive_failures => $consecutive_failures, + consecutive_success => 0, } ); } -sub get($self, $what) { - if ($what eq 'kana') { +sub get( $self, $what ) { + if ( $what eq 'kana' ) { return $self->value; } - if ($what eq 'romanji') { + if ( $what eq 'romanji' ) { return $self->romanji; } return; } sub success($self) { - my $score = $self->score; - my $consecutive_success = $self->consecutive_success + 1; + my $score = $self->score; + my $consecutive_success = $self->consecutive_success + 1; + my $consecutive_failures = 0; $score += 5 + 10 * $consecutive_success; if ( $score > 130 ) { $score = 130; } $self->update( { - score => $score, - consecutive_success => $consecutive_success, + score => $score, + consecutive_success => $consecutive_success, + consecutive_failures => $consecutive_failures, } ); } diff --git a/lib/JapaChar/View/HiraganaKatakanaTestExercise.pm b/lib/JapaChar/View/HiraganaKatakanaTestExercise.pm index 3caa911..498f474 100644 --- a/lib/JapaChar/View/HiraganaKatakanaTestExercise.pm +++ b/lib/JapaChar/View/HiraganaKatakanaTestExercise.pm @@ -16,6 +16,8 @@ use JapaChar::Characters; use Pango; use JapaChar::Random; use JapaChar::Score; +use Digest::SHA qw(sha1_hex); +use Encode qw(encode); use Glib::IO; @@ -95,9 +97,10 @@ sub _new_challenge_romanji($self) { } sub _new_challenge_generic_code( $self, $show, $guess, $can_be_typed = 0 ) { - my $type = $self->_type; - my $grid = $self->_create_grid_challenge; - my $char = JapaChar::Characters->new->next_char($type); + my $type = $self->_type; + my $grid = $self->_create_grid_challenge; + my $char = + $self->_app->characters->next_char( $self->_app->accessibility, $type ); my $kana_label = $self->_get_label_featured_character( $char->get($show) ); my $rng = JapaChar::Random->new->get( 1, 100 ); @@ -205,7 +208,13 @@ sub _get_label_featured_character( $self, $text ) { my $label = Gtk::Label->new($text); my $attr_list = Pango::AttrList->new; my $size = Pango::AttrSize->new( 72 * PANGO_SCALE ); + my $color = Pango::Color->new; + + my $hex_color = sha1_hex(encode 'utf-8', $text); + my ($r, $g, $b) = map { $_ = hex $_; ($_ << 8) | $_ } $hex_color =~ /(..)(..)(..)/; + my $color_attr = Pango::AttrForeground->new($r, $g, $b); $attr_list->insert($size); + $attr_list->insert($color_attr); $label->set_attributes($attr_list); $label->set_halign('center'); return $label; @@ -314,18 +323,24 @@ sub _on_click_continue_button( $self, $grid, $char, $guess ) { } $self->_first_press_continue(0); my $label_feedback; + my $is_repeating = $self->_app->characters->is_repeated; { if ( $self->_final_answer eq $char->get($guess) ) { $label_feedback = Gtk::Label->new('You are doing it great.'); $label_feedback->add_css_class('success'); $self->lesson->add_one_success; - $char->success; + $char->success if !$is_repeating; next; } $label_feedback = Gtk::Label->new( 'Meck!! The correct answer is ' . $char->get($guess) ); $label_feedback->add_css_class('error'); - $char->fail; + $char->fail if !$is_repeating; + } + if ( $is_repeating && $self->_app->characters->last_repeated ) { + + # TODO This is not ideal. + $self->success; } my $attr_list = Pango::AttrList->new; my $size = Pango::AttrSize->new( 23 * $self->_app->get_width ); diff --git a/lib/JapaChar/View/MainMenu.pm b/lib/JapaChar/View/MainMenu.pm index cdb34e9..cf40efd 100644 --- a/lib/JapaChar/View/MainMenu.pm +++ b/lib/JapaChar/View/MainMenu.pm @@ -106,12 +106,21 @@ sub run($self) { $grid->attach( $box_score_basic_lesson, 0, 0, 5, 1 ); $button_start_basic_lesson->set_valign('end'); $button_start_basic_lesson->set_halign('center'); + my $button_assisted_mode = Gtk::Button->new_with_label('Assisted Mode'); $box->set_margin_top(40); $box->append($button_start_hiragana_lesson); $box->append($button_start_katakana_lesson); $box->set_valign('start'); $box->set_halign('center'); $grid->attach( $box, 0, 1, 5, 1 ); + $grid->attach( $button_assisted_mode, 0, 2, 5, 1 ); + $button_assisted_mode->signal_connect('clicked', sub { + $self->app->accessibility->show_assisted_mode_selection; + }); + $button_assisted_mode->set_vexpand(1); + $button_assisted_mode->set_hexpand(1); + $button_assisted_mode->set_valign('center'); + $button_assisted_mode->set_halign('center'); $self->app->window_set_child($grid); } 1;