menu
  Home  ==>  papers  ==>  colibri_utilities  ==>  colibri_hiragana_quiz   

Colibri Hiragana Quiz - Felix John COLIBRI.

  • abstract : a project to accelerate the learning of the Japanese Hiragana alphabet. The user selects a subrange of the 46 characters, a random question is presented and the answer has to be selected among 3* 3 possible solutions. 3 versions: romaji to hiragana, hiragana to romaji, sound to hiragana. Success statistics are computed for each character.
  • key words : Japanese quiz, Japanese Hiragana tutorial, Delphi 6 unicode, playing .wav, playing .mp3
  • software used : Windows XP Home, Delphi 6
  • hardware used : Pentium 2.800Mhz, 512 M memory, 140 G hard disc
  • scope : Delphi 5, 6, 7, 8 Delphi 2005 to 2010, Rad Studio Xe to Xe8, Delpi Seattle, Delphi Berlin
  • level : Delphi developer, Japanese beginner
  • plan :


1 - Learning Japanese Hiragana

My wife and me plan to visit Japan. To be able to talk a little bit with the Japanese people, I decided to start learning Japanese.

This article presents a small utility we wrote to accelerate this training.




1.1 - English, Romaji, Hiragana, Katakana, Kanji

As you might know, the Japanese language uses 3 alphabets :
  • Hiragana, which has around 50 characters
  • Katakana, mostly used for writing foreign origin words (like TV)
  • Kanji which is derived from the Chinese alphabet.
In the tutorials, the pronounciation of the words is often written in "romaji", wich uses our 26 letter "roman" alphabet. Reading a romaji text in english somehow corresponds to Japanese pronounciation. This is not totally correct but reasonable for a first approach.

Most tutorials on the Web or learning books start with some words or simple sentences. Those are first presented in english with a romaji translation. And the Hiragana writing of the romaji is then gradually introduced. And later, the Kanji version, if any, are taught.

For "thank you", the different writings are:

arigatou

In addition

  • the textbooks also indicate that in the case of "thank you", the Kanji writing is unusual
  • you will notice that the Kanji words contains a last Hiragana character


In the any newspaper story, you will find Hiragana, Katakana and Kanji mixed. Here is a copy of some "Yomuri Online" page we randomly picked from their site:

yomuri_online

I have no idea (yet !) of what happened to this unfortunate feller, but it is easy to see that the 3 alphabets are used throughout the text.



Traditionally, Japanese youngsters first learn Hiragana, then gradually learn Kanji.

Unsurprisingly, we decided to follow the same route and try to tackle the Hiragana first. And this is the reason of this project.




2 - The Hiragana alphabet

2.1 - The Hiragana alphabet

The Hiragana characters use the U+3040..U+309F bloc (96 code points) of Unicode. So this is the $3040 .. $309F bloc of the Unicode Bmp (Basic MultiLingual Plane) :

the_kana_bmp_bloc



To display a single Hiragana character, we use the Canvas.TextOutW method. This method uses a pWideChar. This pointer is used to cast a WideChar character that we initialized using a WideChar cast of the code unit

Var l_wide_stringWideString;

  l_wide_string:= ' ';
  l_wide_string[1]:= WideChar(p_code_unit);

  TextOutW(Canvas.Handlep_xp_ypWideChar(l_wide_string), 1);

This fancy legwork could have been avoided with any "post Delphi 2010 version", like Delphi Xe8, but at the cost of a hugely larger .EXE.

Anyway, here is the code:

Procedure c_hiragana_paintbox.display_hiragana_xy(p_xp_yp_code_unitInteger);
  Var l_wide_stringWideString;
  Begin
    If p_code_unitk_error_code_unit
      Then draw_error_code_unit(p_xp_y)
      Else Begin
          l_wide_string:= ' ';
          l_wide_string[1]:= WideChar(p_code_unit);

          TextOutW(Canvas.Handlep_xp_ypWideChar(l_wide_string), 1);
        End;
  End// display_hiragana_xy

Procedure c_all_hiragana_paintbox.handle_paint(p_c_paintboxtObject);

  Procedure _display_column_title;
    Var l_columnInteger;
        l_column_numberString;
    Begin
      For l_column:= 0 To 15 Do
      Begin
        l_column_number:= IntToHex(l_column, 1);
        display_hiragana_xy(f_x(l_column), k_fatOrd(l_column_number[1]));
      End;
    End// _display_column_title

  Procedure _display_hiragana;
    Var l_rowl_columnInteger;
        l_character_code_unitInteger;
    Begin
      l_character_code_unit:= $3040;

      For l_row:= 0 To m_row_count- 1 Do
        For l_column:= 0 To m_column_count- 1 Do
        Begin
          display_hiragana_xy(f_x(l_column), f_y(1+ l_row), l_character_code_unit);
          Inc(l_character_code_unit);
        End;
    End// _display_hiragana

  Begin // handle_paint
    Inherited handle_paint(p_c_paintbox);

    _display_column_title;
    _display_hiragana;
  End// handle_paint



And here is the result:

hiragana_bmp_bloc



Basically, this tables contains

  • the 5 vowels, a i u e o (yes they are not in the traditional latin "a e i o u" order)
  • syllables built using a consonant and the same vowels, like ka, ki, ky etc
This is represented by the classical table:

hiragana_syllables

And

  • each row contains one vowel, for instance a, ka, sa, ta etc
  • each column contains the syllables built using one consonant.
    • for the first column, we have the vowels a i u e o
    • For the "K" column, we have ka, ki, ku, ke, ko
This table is sometimes presented vertically or with some other variations. We will stick to this layout.



As you can see, the hiragana code bloc contains all those characters, but not in the same order. And characters have 0, 1 or 2 accents, using 1, 2 or 3 code units :

hiragana_accents



2.2 - The canonical column presentation

So there is no simple way to transform the code bloc into the traditional column presentation.

We therefore used a simple mapping scheme which is coded like this:

Procedure c_column_hiragana_paintbox.initialize_paintbox(
    p_font_sizep_x_0p_y_0p_row_countp_column_countInteger);

  Procedure _initialize_code_units;
    Var l_c_check_unique_name_listtStringList;

    Procedure _initialize(p_rowp_columnp_code_unitInteger;
        p_nameString);
      Begin
        With m_hiragana_array[p_rowp_columnDo
        Begin
          m_code_unit:= p_code_unit;

          If l_c_check_unique_name_list.IndexOf(p_name)>= 0
            Then display_bug_stop('twice name 'p_name);
          m_name:= p_name;

          If m_name[1]= m_name[2]
            Then m_mnemonic:= m_name[1]
            Else m_mnemonic:= m_name;

          m_row:= p_row;
          m_column:= p_column;
        End;
      End// _initialize

    Const k_vowelArray[0..4] Of Char= ('a''i''u''e''o');
    Var l_rowInteger;

    Begin // _initialize_code_units
      l_c_check_unique_name_list:= f_c_create_sorted_no_duplicate_caseinsensitive_stringlist;

      // -- a
      _initialize(0, 0, $3042, 'aa');
      _initialize(1, 0, $3044, 'ii');
      _initialize(2, 0, $3046, 'uu');
      _initialize(3, 0, $3048, 'ee');
      _initialize(4, 0, $304A'oo');

      // -- ka
      _initialize(0, 1, $304B'ka');
      _initialize(1, 1, $304D'ki');
      _initialize(2, 1, $304F'ku');
      _initialize(3, 1, $3051, 'ke');
      _initialize(4, 1, $3053, 'ko');

      // -- sa
      For l_row:= 0 To 4 Do
        _initialize(l_row, 2, $3055+ l_row* 2, 's'k_vowel[l_row]);

      // -- ta
      _initialize(0, 3, $305F'ta');
      _initialize(1, 3, $3061, 'ti');
      _initialize(2, 3, $3064, 'tu');
      _initialize(3, 3, $3066, 'te');
      _initialize(4, 3, $3068, 'to');

      // -- na
      _initialize(0, 4, $306A'na');
      _initialize(1, 4, $306B'ni');
      _initialize(2, 4, $306C'nu');
      _initialize(3, 4, $306D'ne');
      _initialize(4, 4, $306E'no');

      // -- ha
      _initialize(0, 5, $306F'ha');
      _initialize(1, 5, $3072, 'hi');
      _initialize(2, 5, $3075, 'hu');
      _initialize(3, 5, $3078, 'he');
      _initialize(4, 5, $307B'ho');

      // -- ma
      _initialize(0, 6, $307E'ma');
      _initialize(1, 6, $307F'mi');
      _initialize(2, 6, $3080, 'mu');
      _initialize(3, 6, $3081, 'me');
      _initialize(4, 6, $3082, 'mo');

      // -- ya
      _initialize(0, 7, $3084, 'ya');
      _initialize(2, 7, $3086, 'yu');
      _initialize(4, 7, $3088, 'yo');

      // -- ra
      For l_row:= 0 To 4 Do
        _initialize(l_row, 8, $3089+ l_row'r'k_vowel[l_row]);

      // -- wa
      _initialize(0, 9, $308F'wa');
      _initialize(4, 9, $3092, 'wo');

      // -- n
      _initialize(4, 10, $3093, 'nn');

      l_c_check_unique_name_list.Free;
    End// _initialize_code_units



This will be presented in our paintbox like this:

hiragana_column_presentation

Note that we used the "courrier new" true text font, but other more nice fonts could be used.




3 - The Hiragana Quiz

3.1 - The Objective

We basically want to learn the romaji <-> hiragana mapping :
  • for any romaji, like "ka", find the hiragana character
  • for any hiragana character, find the romaji pronounciation
  • for any sound, find the hiragana character


3.2 - The Quiz layout

Our basic organization, for the hiragana -> romaji or romaji -> hiragana quiz is :
  • randomly present the character
  • present an array of 3 x 3 potential solution
  • the user clicks on a one of the 9 possibilities
    • if the selection is correct, present the character with a mnemonic
    • if the selection is wrong, present the character corresponding to this selection


Here is a romaji -> hiragana example
  • the quiz selects a random character, say "ko", and presents 9 hiragana characters (including "ko") :

    romaji_to_hiragana_quiz_question

  • suppose that the user wrongly selects the bottom right "ke". The quiz displays the romaji corresponding to this letter:

    romaji_to_hiragana_quiz_wrong_answer

  • suppose that the user correctly selects the top right "ko". The quiz confirms the success and optionally displays some mnemonic (COin) :

    romaji_to_hiragana_quiz_correct_answer_ko



The same structure is used for the hiragana -> romaji mapping :



3.3 - The learning range

The 9 random potential solutions are selected in a range of the full hiragana character table.

Usually people learn the alphabet progressively

  • either by column: the vowels, the "K" column, the "S" column etc
  • or by row: the "a" row ("a", "ka", "sa" ...) the "i" row ("i", "ki" etc)
Therefore the 9 random letters must be selected among the letters memorized so far.

To allow this subrange selection, we simply use a "lasso" on the full hiragana map: the user selects by dragging the mouse a rectangular area of the hiragana table. The target letter and the potential solution are then drawn from this map.

In our case, we used the following subrange:

romaji_area_selection

Note that any area of the table is possible: a full column, several columns, any row, the full table. Even a single letter, which is of little interest of course.



3.4 - Playing the Hiragana sound

We started with the Delphi Multi Media Player, but this was cumbersome since it required some Window.

Therefore we selected the direct method Windows MCI primitive:

Function f_play_wave(p_full_file_nameStringp_notify_handletHandle): Boolean;

  Function _f_send_mci_command(p_mci_commandstringp_handletHandle): Boolean;
    Var l_resultInteger;
        l_error_message_bufferArray[0..254] Of char;
    Begin
      display(p_mci_command);
      l_result:= mciSendString(PChar(p_mci_command), Nil, 0, p_handle);
      Result:= l_result= 0;

      If Not Result
        Then Begin
            {get message for returned value}
            mciGetErrorString(l_resultl_error_message_buffer, 255);
            display(Format('*** %d %s', [l_resultStrPas(l_error_message_buffer)]));
            Result:= False;
          End;
    End// _send_mci_command

  Var l_c_file_streamtFileStream;

  Begin // f_play_wave
    Result:= FileExists(p_full_file_name);
    If Not Result
      Then Exit;

    l_c_file_stream:= TFileStream.create(p_full_file_namefmOpenRead);
    Try
    Finally
      l_c_file_stream.Free;
    End;

    display_line;
    display('> f_play_wave 'IntToStr(p_notify_handle));
    (*$B+*)
    Result:=
          _f_send_mci_command('open  'p_full_file_name' alias MyFile', 0)
        Or
          _f_send_mci_command('play MyFile wait', 0)
        Or
          _f_send_mci_command('close MyFile'p_notify_handle);
    (*$B-*)
    display('< f_play_wave');
  End// f_play_wave



3.5 - The mnemonics

We also use mnemonics for each character. Those are picture whose pronounciation starts like the sound of the hiragana, and whose picture somehow contains the outline of the character. We already saw the COin.

To be able to learn each column, we added a mnemonic display, which displays the mnemonic for any column of the canonical table. Here is an example when you click anywhere in the "K" column:

hiragana_mnemonics



3.6 - General outlay

Here is a global view of the application

hiragana_quiz_layouout




4 - The Hiragana Quiz Code

4.1 - The Hiragana Quiz UML Class diagram

Here is the class diagram of the Hiragana Quiz:

hiragana_quiz_uml_class_diagram



4.2 - The architecture

For the code, we had to
  • display the character at some (x, y) position of a tPaintBox, as well for the canonical column table, as for the question, the 3* 3 quiz and the mnemonic recap (a paintbox with one of the column, displaying the hiragana and the associated mnemonic)
  • define a learning area using the mouse drag
  • generate the quiz paintbox containing the potential solutions, generate a random character which is the question
  • get the user mouse selection of one of the quiz cell, and update the answer accordingly


We used the following Classes:
  • c_hiragana_paintbox which is the basic paintbox with mouse to cell conversions (compute the row and column in a hiragana array corresponding to some mouse position)
    • c_all_hiragana_paintbox simply displays the raw katakana unicode bloc
    • c_dynamic_hiragana_paintbox contains the m_hiragana_array, which is a two dimensional dynamic array of t_hiragana_info
      • c_column_hiragana_paintbox displays the canonical hiragana table. This area is used to randomly fill the quiz array with 9 hiraganas
      • c_learning_hiragana_paintbox is the ancestor for the different kinds of quizes
        • c_mnemonic_hiragana_array displays the mnemonics of one column of the canonical array
        • c_quiz_paintbox is the real workorse: when the learning area is selected, it computes the quiz size, fills the quiz with random values and selects on character as the question. It handles the user answer and displays it
          • c_romaji_quiz_paintbox displays a romaji question and expects the selection of the correct hiragana answer
          • c_sound_hiragana_quiz_ancestor contains the two hiragana paintboxes for the question and the answer
            • c_sound_quiz_paintbox plays a sound question and expects the selection of the correct hiragana answer
            • c_hiragana_quiz_paintbox displays a hiragana question and expects the selection of the correct romaji answer


4.2.1 - c_all_hiragana_paintbox

This paintbox simply displays the raw katakana unicode bloc. It was used to find the mapping from the unicode Kana bloc to the canonical column presentation

Procedure c_all_hiragana_paintbox.handle_paint(p_c_paintboxtObject);

  Procedure _display_column_title;
    Var l_columnInteger;
        l_column_numberString;
    Begin
      For l_column:= 0 To 15 Do
      Begin
        l_column_number:= IntToHex(l_column, 1);
        display_hiragana_xy(f_x(l_column), k_fatOrd(l_column_number[1]));
      End;
    End// _display_column_title

  Procedure _display_hiragana;
    Var l_rowl_columnInteger;
        l_character_code_unitInteger;
    Begin
      l_character_code_unit:= $3040;

      For l_row:= 0 To m_row_count- 1 Do
        For l_column:= 0 To m_column_count- 1 Do
        Begin
          display_hiragana_xy(f_x(l_column), f_y(1+ l_row), l_character_code_unit);
          Inc(l_character_code_unit);
        End;
    End// _display_hiragana

  Begin // handle_paint
    Inherited handle_paint(p_c_paintbox);

    _display_column_title;
    _display_hiragana;
  End// initialize_all_hiragana



4.2.2 - c_dynamic_hiragana_paintbox

c_dynamic_hiragana_paintbox contains the m_hiragana_array, which is a two dimensional dynamic array of t_hiragana_info

Each character is managed by the t_hiragana_info record with

  • the code unit of the character. For the "ka" character, the code unit is $304B
  • the romaji name, here ka. To this name is associated a .WAV file for the sound (ka.wav) and a picture for the mnemonic (ka.png which is a shopping CArt)
  • for the mnemonics, the image is stored as a .PNG file (ka.png) and the text ("CArt") is loaded from a mnemonic.txt file
  • there are also fields to gather statistics used to spot the characters which were difficult to learn


This is the code used to display the array :

Procedure c_dynamic_hiragana_paintbox.handle_paint(p_c_paintboxtObject);

  Procedure _display_column_title;
    Var l_columnInteger;
    Begin
      For l_column:= 0 To m_column_count- 1 Do
        display_hiragana_xy(f_x(l_column), k_fat,
            Ord(k_column_letter[1+ l_column]));
    End// _display_column_title

  Procedure _display_hiragana;
    Var l_rowl_columnInteger;
    Begin
      For l_row:= 0 To m_row_count- 1 Do
        For l_column:= 0 To m_column_count- 1 Do
           display_hiragana_xy(f_x(l_column), f_y(m_title_rowl_row),
                m_hiragana_array[l_rowl_column].m_code_unit);
    End// _display_hiragana

  Begin // handle_paint
    // -- draws the backgroud and a grid
    Inherited handle_paint(p_c_paintbox);

    If m_title_row> 0
      Then _display_column_title;
    _display_hiragana;
  End// handle_paint



c_column_hiragana_paintbox

c_column_hiragana_paintbox displays the canonical hiragana table in the right panel of the Form. This display is used for all the quizes. The user selects a learning area of this canonical display, and this selection triggers the learning sequence.

And

  • the display of the selected area is quite standard and has been presented many times in this site ( Delphi 3d designer, Selection Rectangle. In addition we trigger the quiz initialization:

    Procedure c_column_hiragana_paintbox.handle_mouse_down(p_c_senderTObject;
        p_mouse_buttonTMouseButtonp_shfit_stateTShiftStatep_xp_yInteger);
      Begin
        m_start_row:= f_y_to_row(p_y);
        m_start_column:= f_x_to_column(p_x);

        m_in_mouse_move:= True;
      End// handle_mouse_down

    Procedure c_column_hiragana_paintbox.handle_mouse_up(p_c_senderTObject;
        p_mouse_buttonTMouseButtonp_shfit_stateTShiftStatep_xp_yInteger);
      Begin
        If m_in_mouse_move
          Then Begin
              // -- update the end selection coordinates
              m_end_row:= 1+ f_y_to_row(p_y);
              m_end_column:= 1+ f_x_to_column(p_x);

              m_selected_count:= (m_end_rowm_start_row)* (m_end_columnm_start_column);

              // -- trigger the initialization of the quiz paintbox
              If (m_selected_count> 0) And Assigned(m_on_notify_selection_changed)
                Then m_on_notify_selection_changed;

              // -- stop tracking the mouse
              m_in_mouse_move:= False;
            End;
      End// handle_mouse_up

  • select_row_column_1_2 is used to select the first column as a default selection learning area when the quiz paintbox is created


4.2.3 - c_learning_hiragana_paintbox

The c_learning_hiragana_paintbox simply contains a reference to the canonical hiragana display. This link is required to access the selected area in order to fill the quiz with randomly selected items from this area



4.2.4 - c_mnemonic_hiragana_array

c_mnemonic_hiragana_array is used to display the mnemonics of one column (see mnemonic above):
  • the user clicks on any column of the
  • the hiragana and the mnemonic of each hiragana are displayed
This was added to help the learning of each column of characters



4.2.5 - c_quiz_paintbox

c_quiz_paintbox is used to manage each of the 3 quizes
  • when the learning area is selected, it computes the quiz size. The quiz array can be 1* 1, 1* 2, 1* 3, 2* 3 or 3* 3, depending on the size of the selected area. Here is the sizing code

    Procedure c_quiz_paintbox.handle_learning_area_changed;
        // -- the user changed the selected area
        // -- adjust the quiz paintbox

      Procedure _compute_quiz_array_size;
          // -- compute the quiz array size, depending on the selection count
        Var l_quiz_row_countl_quiz_column_countInteger;
            l_selected_countInteger;
        Begin
          l_quiz_row_count:= 0;
          l_quiz_column_count:= 0;

          With m_c_column_hiragana_paintbox_ref Do
          Begin
            If m_empty_count> 0
              Then Begin
                  // -- adjust the selection count for the sizing,
                  // --   keep the original for any other purpose
                  l_selected_count:= m_selected_countm_empty_count;
                End
              Else l_selected_count:= m_selected_count;

            If (l_selected_count= 0) Or (m_selected_count= 0)
              Then force_first_column_selection
              Else
                If l_selected_count< 3
                  Then Begin
                      l_quiz_row_count:= Min(l_selected_count, 3);
                      l_quiz_column_count:= 1;
                      // -- initialize the quiz
                      Self.initialize_empty_hiragana_array;
                    End
                  Else Begin
                      l_quiz_row_count:= 3 ;

                      If l_selected_count< 6
                        Then l_quiz_column_count:= 1
                        Else
                          If l_selected_count< 9
                            Then l_quiz_column_count:= 2
                            Else l_quiz_column_count:= 3;
                      // -- initialize the quiz
                      Self.initialize_empty_hiragana_array;
                    End;
          End// with m_c_column_hiragana_paintbox_ref

          m_row_count:= l_quiz_row_count;
          m_column_count:= l_quiz_column_count;

          // -- this allocates the quiz array
          SetLength(m_hiragana_arraym_row_countm_column_count);
        End// _compute_quiz_array_size

      Begin // handle_learning_area_changed
        // -- recompute the column count
        _compute_quiz_array_size;

        // -- recompute the quiz size
        Height:= k_fatm_row_count* (m_character_height+ 2* k_fat);
        Width:= k_fatm_column_count* (m_character_width+ 2* k_fat)+ k_fat;
        display_line;

        fill_quiz_and_choose_random;
      End// handle_learning_area_changed

  • after the creation of a new quiz, or after a successful answer, the quiz is filled with random characters from the learning area :

    Procedure c_quiz_paintbox.handle_learning_area_changed;
        // -- the user changed the selected area
        // -- adjust the quiz paintbox

      Procedure _compute_quiz_array_size;
          // -- compute the quiz array size, depending on the selection count
        Var l_quiz_row_countl_quiz_column_countInteger;
            l_selected_countInteger;
        Begin
          l_quiz_row_count:= 0;
          l_quiz_column_count:= 0;

          With m_c_column_hiragana_paintbox_ref Do
          Begin
            If m_empty_count> 0
              Then Begin
                  // -- adjust the selection count for the sizing,
                  // --   keep the original for any other purpose
                  l_selected_count:= m_selected_countm_empty_count;
                End
              Else l_selected_count:= m_selected_count;

            If (l_selected_count= 0) Or (m_selected_count= 0)
              Then force_first_column_selection
              Else
                If l_selected_count< 3
                  Then Begin
                      l_quiz_row_count:= Min(l_selected_count, 3);
                      l_quiz_column_count:= 1;
                      // -- initialize the quiz
                      Self.initialize_empty_hiragana_array;
                    End
                  Else Begin
                      l_quiz_row_count:= 3 ;

                      If l_selected_count< 6
                        Then l_quiz_column_count:= 1
                        Else
                          If l_selected_count< 9
                            Then l_quiz_column_count:= 2
                            Else l_quiz_column_count:= 3;
                      // -- initialize the quiz
                      Self.initialize_empty_hiragana_array;
                    End;
          End// with m_c_column_hiragana_paintbox_ref

          m_row_count:= l_quiz_row_count;
          m_column_count:= l_quiz_column_count;

          // -- this allocates the quiz array
          SetLength(m_hiragana_arraym_row_countm_column_count);
        End// _compute_quiz_array_size

      Begin // handle_learning_area_changed
        // -- recompute the column count
        _compute_quiz_array_size;

        // -- recompute the quiz size
        Height:= k_fatm_row_count* (m_character_height+ 2* k_fat);
        Width:= k_fatm_column_count* (m_character_width+ 2* k_fat)+ k_fat;
        display_line;

        fill_quiz_and_choose_random;
      End// handle_learning_area_changed
    Procedure c_quiz_paintbox.fill_quiz_and_choose_random;

      Procedure _fill_quiz;
        Var // -- do not allow duplicate item in the quiz
            l_unique_quiz_romanjj_listtStringList;

            // -- the position of the next item in the quiz
            l_quiz_rowl_quiz_columnInteger;
            // -- the position of the item in the learning area
            l_learning_rowl_learning_columnInteger;

            l_candidate_hiragana_infot_hiragana_info;
        Begin
          // -- debug : fill the array with errors
          For l_quiz_row:= 0 To m_row_count- 1 Do
            For l_quiz_column:= 0 To m_column_count- 1 Do
              m_hiragana_array[l_quiz_rowl_quiz_column].m_code_unit:= k_error_code_unit;

          l_unique_quiz_romanjj_list:= tStringList.Create;

          // -- fill all the cells of the quiz
          For l_quiz_row:= 0 To m_row_count- 1 Do
            For l_quiz_column:= 0 To m_column_count- 1 Do
            Begin
              // -- in the selected area, find an item which is not null
              // --   and has not been already selected
              Repeat
                // -- try a random value
                With m_c_column_hiragana_paintbox_ref Do
                Begin
                  list_learning_area;
                  l_learning_row:= m_start_rowRandom(m_end_rowm_start_row)- 1;
                  l_learning_column:= m_start_columnRandom(m_end_columnm_start_column);
                  // -- accept if not null and not already in the quiz
                  If m_c_column_hiragana_paintbox_ref.f_selected_out_of_range(1+ l_learning_rowl_learning_column)
                    Then display_bug_stop(Format('      quiz_   %2d %2d',
                        [l_learning_rowl_learning_column]));
                  l_candidate_hiragana_info:= m_hiragana_array[l_learning_rowl_learning_column];
                End// with m_c_column_hiragana_paintbox_ref

                With l_candidate_hiragana_info Do
                  If (m_code_unit<> 0) And (l_unique_quiz_romanjj_list.IndexOf(m_name)< 0)
                    Then Begin
                        l_unique_quiz_romanjj_list.Add(m_name);

                        m_hiragana_array[l_quiz_rowl_quiz_column]:= l_candidate_hiragana_info;
                        Break;
                      End;
              Until False;
            End// for l_row, for l_column

          l_unique_quiz_romanjj_list.Free;
        End// _fill_quiz

      Procedure _choose_random;
        Var l_random_rowl_random_columnInteger;
        Begin
          Repeat
            l_random_row:= Random(m_row_count);
            l_random_column:= Random(m_column_count);
            m_random_question_info:= m_hiragana_array[l_random_rowl_random_column];

            // -- quit either if only 1 cell or if different from previous learning sequence
            If     (m_row_countm_column_count= 1)
                Or
                   (m_random_question_info.m_name<> m_previous_random_question_info.m_name)
              Then Break;
          Until False;

          // -- display emply item
          If Assigned(m_on_notify_mouse_down)
            Then
              With m_c_column_hiragana_paintbox_ref Do
                m_on_notify_mouse_down(m_empty_hiragana_info);

          m_success_count:= 0;
          m_previous_random_question_info:= m_random_question_info;

          draw_question;
        End// _choose_random

      Begin // fill_quiz_and_choose_random
        _fill_quiz;
        _choose_random;
      End// fill_quiz_and_choose_random

    Special care is taken to skip empty cells in the canonical array (the "Y", "W" and "N") columns.

    In addition, when the quiz has been filled with random values, one of them is selected as the question. The question must be different from the previous one.

    And when the question has been chosen, the draw_question method is called to refresh the question display

  • when the user click on on of the quiz cell:
    • either the answer is wrong, and the wrong answer is displayed in red
    • or the answer is correct and the correct answer is displayed in green. By default, the user has to click a second time to erase this answer and start a new question, but there is a m_direct_answer boolean which avoids this second click (when the answer is correct, the next question is directly presented)
    In both cases, the statistics of this letter are updated.


4.2.6 - c_romaji_quiz_paintbox

The c_romaji_quiz_paintbox displays a romaji question and expects the selection of the correct hiragana answer

romaji_question



The c_romaji_quiz_paintbox contains

  • the quiz paintbox (from its c_dynamic_hiragana_paintbox)
  • the m_c_question_edit and m_c_answer_edit tEdits
  • the overridden to display the question, display the answer, clear the answer


4.2.7 - c_sound_hiragana_quiz_ancestor

This class contains the two hiragana paintboxes for the question and the answer



4.2.8 - c_sound_quiz_paintbox

c_sound_quiz_paintbox plays a sound and expects the user to select the correct hiragana. In the following figure, the sound "ee" was played and the user correctly selected the "e" hiragana :

sound_question

and

  • the draw_question only plays the sound


4.2.9 - c_hiragana_quiz_paintbox

c_hiragana_quiz_paintbox presents a hiragana character, and expects the selection of the correct romaji:

hiragana_question

Here the paint method of the quiz has to be overridden to display the romaji instead of the default hiragana




5 - Mini How To

5.1 - General layout

Here is the general layout :

hiragana_quiz_true

5.2 - The Mnemonic recap

To display the mnemonics of one column
   start the exe
   select the "mnemonic" RadioButton
   the mnemonics of the first column are displayed
To display another column
   in the canonical display, click on any column
   the mnemonics of this column are displayed
Here is the display of the "K" column:

hiragana_mnemonic_display



5.3 - The 3 quizes

To use a quiz
   start the exe
   select one of the 3 quiz type by clicking one of the radiobuttons : romaji_quiz_, hiragana_quiz_ or sound_quiz_
   then
  • the canonical hiragana array is displayed on the right, and by default the 5 characters of the first column are selected as the learning area
  • the quiz array with its question question and answer displays are presented. The quiz array contains 3 choices, since the learning area only contains 5 characters
   you can start answering the question. And
  • clicking on the wrong cell displays the corresponding answer in red.
  • clicking on the right cell confirms the answer in green. Clicking a second time on this cell presents the next text


To change the learning area
   click and drag the mouse over the canonical area
   the quiz array is presented, and, depending on the learning area size, this array can contain be 1, 2, 3, 6 or 9 cells
The rest of the quiz is handled as before



Here is an example of selecting some learning area of the hiragana to romaji quiz with a wrong answer

haragana_quiz_learning_area_selection

This is the display if the user selects some wrong answer :

haragana_quiz_wrong_answer

and here is the correct answer :

haragana_quiz_correct_answer



To change the quiz type, click on another radiobutton



The learning sequence is

  • 1 - select a quiz
  • 2 - select a learning area
  • 3 - a question is presented
  • 4 - select an answer in the quiz

    If the selection is wrong, the wrong character will be presented in red
    If the selection is right, the correct character will be presented in green, and clicking a second time on the same area presents a new question

  • 5 - the quiz displays the right or wrong answer, along with the mnemonic
and
  • for a given learning area, you can run as many questions as you want
  • for a given quiz, you can select any learning area
  • you can change the quiz



6 - Some Remarks

6.1 - About the code

The Class structure seems overly complex for this quiz family. The only alternate solution would have been to add a "question / answer" display Class, with 2 descendents: text or hiragana. This hardly simplifies the structure.

Using a Delphi Unicode version could have simplified the display of all the Kana unicodes. We could have used

  • a tStringGrid for the canonical display and the quiz
  • tEdits for all the questions and answers.
At some places the use of events could have been replaced by Abstract methods in the ancestor.



6.2 - The learning efficiency

This part is obviously very personal.

To recap the learning experience:

  • I started with the excellent Learn Japanese Language Free and Easy site.

    The first Hiragana lesson teach you each column in turn.

    To memorize the first column, I looked at the mnemonics.

    For the second column, I felt necessary to write every letter on a piece of paper.

    For the third, I started to forget the previous columns.

  • I then created the quiz, to check that all previously learned character were still remembered

  • I then added the hiragana to romaji quiz and the sound to hiragana quiz


So it took me around a week or so to learn the hiragana glyphs. On internet, some people (or school advertising) brag about a 1 day effort. I am not sure I could accomplish this. My lessons were around 1 hour for learning and writing a new column. Then I checked the previously learned letters with the quiz. But then there were some mental rehearsal during the day (walking, or musing at a bus stop). So if I learned in around 10 days, it took more then 10 hours : the mental training, the time to let it "sink in", the sleep rehearsal (awaking during the night thinking about those glyphs / sounds / mnemonics). Certainly the japanese adult schools with new students every week must have a better idea about the average performance, the difficulties, the best technique to use.



Learning the Hiragana alphabet is only the very first step. So I started to learn some vocabulary (train, subway, water...), and some "survival" phrases (where is the nearest subway station ?).

For the vocabulary, the Learn Japanese Language Free and Easy site is again very helpful.

For the phrases, I found the Japanese Phrases for Travellers site very complete. It has over 1.200 phrases.



Both the vocabulary and the sentence were incorporated in 2 other Delphi applications to avoid the download times. This might be published later.



So I now feel reasonably ready to fly to Japan, just at Hanami time (cherry blossom). Should you recognize my French béret (a hat like the Argentinian Gauchos)

beret

near Takayama or Miyajima, do not hesitate to meet me ! I will be only too happy to try my very very basic Japanese. Hai !.




7 - Download the Sources

This is the first time you can download the .EXE from this site. Some people might want to use the quiz directly without going through a Delphi compilation.

Here are the source code files:

You can also download the .EXE (this is a first for this site): The .ZIP file(s) contain:
  • the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
  • any .TXT for parameters, samples, test data
  • all units (.PAS) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
  • will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
  • create or select any folder of your choice
  • unzip the downloaded file
  • using Delphi, compile and execute
To remove the .ZIP simply delete the folder.

The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lass etc. This notation is presented in the Alsacian Notation paper.
The .ZIP file(s) contain:

  • the main program (.DPROJ, .DPR, .RES), the main form (.PAS, .ASPX), and any other auxiliary form or files
  • any .TXT for parameters, samples, test data
  • all units (.PAS .ASPX and other) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path outside from the container path creation etc).
To use the .ZIP:
  • create or select any folder of your choice.
  • unzip the downloaded file
  • using Delphi, compile and execute
To remove the .ZIP simply delete the folder.

The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lass etc. This notation is presented in the Alsacian Notation paper.



As usual:

  • please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will be helpful for other readers
  • we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
  • or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
    Name :
    E-mail :
    Comments * :
     

  • and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.



8 - Links

Here are a couple of links
  • Learn Japanese Language Free and Easy the best site I found so far, complete whith Hiragana, Katakana, vocabulary, grammar etc. I only scratched the surface of its treasures

  • Japanese Phrases for Travellers has over 1.200 "survival phrases", phrases, organized by topic (restaurant, travel etc). The main advantages were
    • its completeness
    • the relevance of the phrases for a traveller
    • the presence of sound (mp3)
    • the presentation of Hiragana and Kanji (where relevant) for each phrase
  • I also subscribed to JapanesePod101 site. I had a hard time to understand if it really was free, as claimed. It turned out that I had to pay a $1 charge for "bandwidth". So I accepted. I did not use the site very much, since I quickly understood that listening to videos was not enough: some pen and pencil rehearsals were still required. So I wrote the quiz. Then, while I still was battling with my Delphi quiz, I was charged $25 "premium" whatever. I cancelled everything and asked for a refund. The send me a mail that the refund was performed, and I still have to check. I dislike those "free" with all kind of formulas where I did not see the additional charges. Anyway, some reviews were reasonably favorable for a "quick learning" of Japanese.

  • I also bought a couple of books
    • a couple of books about sightseeing
    • then "Japanese 1 from zero" by George trombley Jr and Yukari Katakana. I just read up to page 70 so far, but this presented ms the "wa", "ka", "kore" constructs, which were about enough to start understand and rehearse the "survival phrases"
On the coding side, for Unicode we published
  • Delphi Unicode Migration which presents the unicode system (code points, code units, Basic Multilingual Plane, Utf8, Utf16, surrogates and composites, and the points to watch for when migrating to a post Delphi 2009 version)



9 - The author

Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi Xe_n migrations, refactoring), Delphi Consulting and Delph training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP  /  UML, Design Patterns, Unit Testing training sessions.
Created: mar-17. Last updated: mar-2017 - 94 articles, 229 .ZIP sources, 1183 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2017. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
      – delphi_net_bdsproj
      – dccil_bat_generator
      – coliget_search_engine
      – dfm_parser
      – dfm_binary_to_text
      – component_to_code
      – exe_dll_pe_explorer
      – dll_process_viewer
      – the_alsacian_notation
      – html_help_viewer
      – cooking_the_code
      – events_record_playback
      – colibri_hiragana_quiz
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
    + vcl
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog