Forum.iFiction.Ru

iFiction.Ru · ifHub · FAQ · IFWiki · QSP · URQ · INSTEAD · AXMA

форум об interactive fiction, текстовых приключенческих играх и всём таком...

Вы не зашли.

0    0    #1
05.12.2007 12:55

Flint
Участник
Зарегистрирован: 06.09.2007
Сообщений: 148

---

Универсальный spellchecker для RTADS

Людям свойственно ошибаться. При наборе, к примеру, мы частенько делаем опечатки. В этом случае текстовые процессоры подчеркивают неизвестные слова, а поисковая система Google предлагает вариант исправления для неправильного слова.

Интерфейс всех парсерных игр основан на вводе текста, причем количество введенного игроком текста за всю игру может быть весьма существенным. Естественно, не обходится без ошибок. «К сожалению, слово "деференциал" мне неизвестно.» Довольно странным выглядит то, что эта проблема никак не решается в этой абсолютно текстовой среде, где, вроде бы, сам бог велел (а ведь сейчас проверку орфографии встраивают даже в браузеры), тем более, что никаких словарей для проверки таскать за собой не надо – словарь, вот ведь он, заботливо объявленный автором в самой игре!

Поэтому я решил написать нечто вроде spellchecker для RTADS.
Он исправляет опечатки следующих типов:
1) пропустили букву
>о холдильник

2) набрали лишнюю букву
>о холодлильник

3) перепутали буквы местами (частенько случается при наборе)
>о холдоильник

4) ошиблись в букве
>о халодильник

После того, как вы нажмете Энтер, на экране, вместо раздражающего

«К сожалению, слово "халодильник" мне неизвестно.»

вы увидите

Возможно, вы имели в виду «холодильник».

Высокий белый холодильник. На боковую стенку налеплен магнит в виде головы панды (довольно дурацкий, верно?).


Мой spellchecker исправляет только одну ошибку на слово (т.е. «халодильник» он исправит, а «халадильник» уже нет). Это связано с объемом вычислений. Для слова из 7 букв выполняется 478 проверок на одну ошибку, соответственно, для проверки на 2 ошибки необходимо производить 478^2 = 228484 проверок, что конкретно затормаживает игру. К тому же, по статистике Google, из всех ошибок при вводе ошибки в одну букву составляют более 90% случаев, так что большинство опечаток мой spellchecker исправит.

Спелчекер универсален – он использует активный словарь текущей игры, обеспечивая тем самым 100% релевантность исправлений и минимум ложных срабатываний :-).

Чтобы подключить spellchecker к вашей игре на RTADS, сделайте следующее (процесс описан для файлов 24 релиза библиотек, но, скорее всего, заработает и на более ранних):

1. Откройте файл advr.t, найдите там функцию preparse и в самый ее конец, но перед “return comStr;” воткните
global.prevCommand := comStr;

2. Откройте файл erroru.t и в самый конец файла добавьте

parseErrorParam: function(errornum, string, ...)
{
    local correct, pos, newstr;
   
    if (errornum != 2)
        return parseError(errornum, string);
   
    correct := spellcheck(getarg(3));
    if (correct)
    {
        pos := reSearch(getarg(3), global.prevCommand);
        newstr := substr(global.prevCommand, 1, pos[1] - 1) + correct + substr(global.prevCommand, pos[1] + pos[2], length(global.prevCommand));
       
        "Возможно, вы имели в виду <b>&laquo;<<correct>>&raquo;</b>.<br><br>";
        parserReplaceCommand(newstr);
    }
   
    return parseError(errornum, string);
}

spellcheck: function(word)
{
    local ruslet := 'абвгдежзиклмнопрстуфхцчшщьыъэюя';
    local englet := 'abcdefghijklmnopqrstuvwxyz';
    local curlet := ruslet;
    local i, j;
   
    local variants = [];
    local found;
   
    //узнаем, английский ли это текст или нет
    for (i := 1; i <= length(word); i++)
    {
        for (j := 1; j <= length(englet); j++)
        {
            if (substr(word, i, 1) = substr(englet, j, 1))
            {
                curlet := englet;
                goto next;
            }
        }
    }
    next: ;
   
    //пропущенные буквы
    for (i := 1; i <= length(curlet); i++)
    {
        for (j := 0; j <= length(word); j++)
        {
            variants += substr(word, 1, j) + substr(curlet, i, 1) + substr(word, j + 1, length(word));
        }
    }
   
    //лишние буквы
    for (i := 0; i < length(word); i++)
    {
        variants += substr(word, 1, i) + substr(word, i + 2, length(word));
    }
   
   
    //неправильные буквы
    for (i := 1; i <= length(curlet); i++)
    {
        for (j := 0; j < length(word); j++)
        {
            variants += substr(word, 1, j) + substr(curlet, i, 1) + substr(word, j + 2, length(word));
        }
    }
   
    //перепутанные местами буквы
    for (i := 0; i < length(word) - 1; i++)
    {
        variants += substr(word, 1, i) + substr(word, i + 2, 1) + substr(word, i + 1, 1) + substr(word, i + 3, length(word));
    }
   

    for (i := 1; i <= length(variants); i++)
    {
        found := parserDictLookup([] + variants[i], [PRSTYP_NOUN]);
        if ( length(found) > 0)
            return variants[i];
    }
   
    for (i := 1; i <= length(variants); i++)
    {
        found := parserDictLookup([] + variants[i], [PRSTYP_VERB]);
        if ( length(found) > 0)
            return variants[i];
    }

    for (i := 1; i <= length(variants); i++)
    {
        found := parserDictLookup([] + variants[i], [PRSTYP_ADJ]);
        if ( length(found) > 0)
            return variants[i];
    }
   
    return nil;
}

Все! Компилируйте игру и можете пробовать.

Из недостатков спелчекера стоит отметить то, что он не занимается подбором форм слов, поэтому возможно неграмотное с точки зрения русского языка исправление написания, например «осмотреть сантехникв» (хотели написать «осмотреть сантехника») будет исправлено на «осмотреть сантехник». К сожалению, это никак не исправить.

Просьба протестировать работу на имеющихся у вас проектах RTADS. Пожелания и предложения приветствуются.

И напоследок, может кто-нибудь знает, как убрать вывод сообщения [TADS-1014: 'abort' statement executed] при каждом вызове функции parserReplaceCommand?

RTADS – лучшая платформа!

Неактивен

0    0    #2
06.12.2007 05:43

uux
Участник (+836, -80)
Откуда: Москва
Зарегистрирован: 02.12.2006
Сообщений: 1584

Re: Универсальный spellchecker для RTADS

goraph написал:

Класс, потестировал, на первый взгляд вроде хорошо работает, думаю использовать smile
только сообщение 1014 надо както убрать, почему оно возникает не понял sad

Гор, ты копал в правильном направлении: это, похоже, недоизжитый глючок TADS - видимо, Майкл Робертс не предполагал, что кто-то будет вызывать parserReplaceCommand из функции обработки ошибки;).

Есть вариант обхода: вынести спелл-чек в специально предназначенные для этого функции parseUnkownVerb,  parseUnknownDobj и parseUnknownIobj. Подробнее см. в мануале. Пример кода для parseUnkownVerb (естественно, бессовестный плагиат у Flint'а):

Код:

parseUnknownVerb: function(actor, wordlist, typelist, errornum)
{
    local correct, pos, newstr;
    
 //   if (errornum != 2)
 //       return nil;
    
    correct := spellcheck(wordlist[1]);
    if (correct)
    {
        pos := reSearch(wordlist[1], global.prevCommand);
        newstr := substr(global.prevCommand, 1, pos[1] - 1) + correct + substr(global.prevCommand, pos[1] + pos[2], length(global.prevCommand));
        
        "Возможно, вы имели в виду \(\<<<correct>>\>\).\b\b";
        parserReplaceCommand(newstr);
    }
    
    return nil;
}

В таком варианте разбираются ошибки только в глаголе, ошибка TADS-1014 не возникает (вроде бы). Для существительных, как я уже говорил, надо будет добавить функции parseUnknownXobj.

И еще чуть-чуть про оптимизацию (точнее, мелкая придирка). Если заменить строчку

"Возможно, вы имели в виду <b>&laquo;<<correct>>&raquo;</b>.<br><br>";

на

"Возможно, вы имели в виду \(\<<<correct>>\>\).\b\b";

то все будет корректно отображаться даже в том случае, если в игре отключен HTML-вывод;).

Неактивен

0    0    #3
06.12.2007 19:47

uux
Участник (+836, -80)
Откуда: Москва
Зарегистрирован: 02.12.2006
Сообщений: 1584

Re: Универсальный spellchecker для RTADS

goraph написал:

uux, твой вариант ошибку 1014 конечно исправляет, но теперь при вводе просто существительного (без глаголов) возникает "стек оверфлоу" smile

Исправленная версия (измененные строки выделены зеленым):


parseUnknownVerb: function(actor, wordlist, typelist, errornum)
{
    local correct, pos, newstr;
   
   if (((typelist[1] & PRSTYP_UNKNOWN)=0) and ((typelist[1] & PRSTYP_VERB)=0))
        return nil;
   
    correct := spellcheck(wordlist[1]);
    if (correct)
    {
        pos := reSearch(wordlist[1], global.prevCommand);
        newstr := substr(global.prevCommand, 1, pos[1] - 1) + correct + substr(global.prevCommand, pos[1] + pos

[2], length(global.prevCommand));
       
        "Возможно, вы имели в виду \(\<<<correct>>\>\).\b\b";
        parserReplaceCommand(newstr);
    }
   
    return nil;
}


"Зеленое" условие означает: если первое слово не является глаголом и не является неизвестным, проверку не делаем.

UPD.: на самом деле проверка

Код:

(typelist[1] & PRSTYP_VERB)=0

избыточна - если первое слово - знакомый игре глагол, то parseUnknownVerb вообще не будет вызываться.

Отредактировано uux (07.12.2007 00:46)

Неактивен

0    0    #4
08.12.2007 21:27

uux
Участник (+836, -80)
Откуда: Москва
Зарегистрирован: 02.12.2006
Сообщений: 1584

Re: Универсальный spellchecker для RTADS

goraph написал:

uux, не знаю, уж закончим ли мы когда-нибудь smile
Для глаголов твой вариант работает замечательно, но при попытке реализации parseUnknownDobj (модифицировать я пытался deepverb), возникает все та же ошибка 1014 sad

Гор, ты прав. Боюсь, что не закончим;). Гневное письмо Майку Робертсу буду писать (тем более, что в ходе перевода TADSовского референс-гайда и тестирования описанных в нем функций наткнулся еще на пару глючков).

Могу представить, почему так происходит: Робертс считал, что методы parseUnknownXobj должны не заменять всю команду, а сами осуществлять подбор нужного объекта в игре с использованием имеющихся слов (и, соответственно, не тестировал вызов parserReplaceCommand из них). В связи с этим к проблеме можно попытаться подойти с другой стороны.

Писать код прямо сейчас у меня возможности нет (если совсем край - пиши на форум, постараюсь в течение недели что-нибудь сделать), но могу подсказать общий план действий: после вызова спелл-чекера в parseUnknownXobj замени в исходном списке слов неправильное на правильное и выполни подбор объекта сам. Описание необходимых функций - в главе 4.2 мануала, "Более сложные технические приемы синтаксического анализа", подраздел "Обработка введенной строки непосредственно из игры". Обрати внимание на функции parseNounList, parserResolveObjects. Единственное - parseNounList глубоко параллельны специфические моменты русскозяычной обработки, так что, возможно, придется перенести туда часть обработки из preparseCmd...

Как сложно-то все это, блин;(. Кстати, использование parserReplaceCommand в preparseCmd и parseNounPhrase также вызывает нашу любимую ошибку 1014, чему вообще никакого логического объяснения подобрать нельзя... Учитесь делать меньше опечаток, господа игроки;).

Отредактировано uux (08.12.2007 21:28)

Неактивен

0    0    #5
10.12.2007 09:52

uux
Участник (+836, -80)
Откуда: Москва
Зарегистрирован: 02.12.2006
Сообщений: 1584

Re: Универсальный spellchecker для RTADS

Появилась свежая мысль, как подойти к решению проблемы совсем с другой стороны (через ж..., извиняюсь за грубость;): оставить все так, как изначально было у Flint'а (т. е. вызов спелл-чекера осуществлять в parseErrorParam), но перехватывать выводимый текст при помощи outcapture (ее описание см. в непереведенной части мануала - "Справочник по языку программирования") и безжалостно вырезать из него сообщение об ошибке. Практическое воплощение этой идеи постараюсь запостить до конца недели.

UPD: "свежая" идея не сработает;(. Сообщения о системных ошибках outcapture и outhide не перехватывают. Так что придется вернуться к варианту "А".

Отредактировано uux (10.12.2007 11:01)

Неактивен

0    0    #6
31.12.2007 15:48

uux
Участник (+836, -80)
Откуда: Москва
Зарегистрирован: 02.12.2006
Сообщений: 1584

Re: Универсальный spellchecker для RTADS

Ох... Отдаю последний должок перед IF-сообществом за уходящий год. Неожиданно затянувшаяся цепочка событий, требовавшая всего моего внимания, наконец завершилась. Итак, метод parseUnknownDobj, обходящий проблему с заменой команды, выглядит примерно так:


Код:

modify deepverb
parseUnknownDobj(actor, prep, iobj, wordlist)=
{
    local nounlist, adjlist, wordlistlen, i, pos, newstr, correct, newwordlist, newTypeList, finalobj,
          retobj, j, sublist;
    wordlistlen:=length(wordlist);
    i:=1;
    newwordlist:=[];
    while(i<=wordlistlen)
      {// Начинаем поиск неизвестных слов
       nounlist:=parserDictLookup([wordlist[i]], [PRSTYP_NOUN]);   
       adjlist:=parserDictLookup([wordlist[i]], [PRSTYP_ADJ]);
       if((nounlist=[]) and (adjlist=[]))  // Т. е. слово отсутствует в словаре
         {correct := spellcheck(wordlist[i]); // Пытаемся исправить его, используя спелл-чекер
          if(correct)
            {// Удалось; выводим сообщение спелл-чекера, заносим исправленное слово в новый список
             //pos := reSearch(wordlist[i], global.prevCommand);
             //newstr := substr(global.prevCommand, 1, pos[1] - 1) + correct + substr(global.prevCommand, pos[1] + pos[2], length(global.prevCommand));
             "\(<<wordlist[i]>>\): возможно, вы имели в виду \(\<<<correct>>\>\).\b\b";

             //parserReplaceCommand(newstr);

             newwordlist:=newwordlist+correct;
            }
          else
            {// Исправить слово не удалось; прерываем обработку
             "Я не понимаю слова \"<<wordlist[i]>>\".";
             return true;
            }
         }
       else
         {// Слово не требует исправления - заносим его в новый список без изменений
          newwordlist:=newwordlist+wordlist[i];
         }
       i:=i+1;
      }
    // Теперь начинаем подбор объектов для исправленного словосочетания
    // Сначала получаем новый список типов лексем
    newTypeList:=parserGetTokTypes(newwordlist);
    // Вызываем встроенную функцию подбора объектов для словосочетания
    finalobj:=parseNounList(newwordlist, newTypeList, 1,
                       true, true, nil);
    if(finalobj=nil)
      {// Некорректное словосочетание; сообщение об ошибке уже выведено, дальнейшей обработки не требуется
       return true;
      }
    else
      {// В противном случае возвращается список хотя бы с одним элементом
       if(length(finalobj)=1)
         {// Список состоит из одного элемента; значит, обработка завершилась неудачно. Анализируем,
          // надо ли выдавать сообщение об ошибке
          if(finalobj[1]=1)
            {// Система не выдавала сообщения об ошибке - его надо сгенерировать самостоятельно
             "Я не понимаю это предложение.";
             return true;
            }
          else
            {// Сообщение уже выдавалось - дальнейших действий не требуется
             return true;
            }
         }
       else
         {// В противном случае подбор завершился успешно - возвращаем список подобранных объектов
          i:=1;
          retobj:=[];
          while(i<=length(finalobj))
            {if(datatype(finalobj[i])=2)
               {// Переносим в возвращаемый список только объекты
        retobj:=retobj+finalobj[i];
               }
             if(datatype(finalobj[i])=7)
               {// Подчиненный список; просматриваем его на наличие объектов
                j:=1;
                sublist:=finalobj[i];
                while(j<=length(sublist))
                  {if(datatype(sublist[j])=2)
                     {retobj:=retobj+sublist[j];
                     }
                   j++;
                  }
               }
             i++;
            }
          return retobj;
         }
      }    
}
;

Описания используемых здесь функций подбора можно посмотреть в русском мануале, раздел 4.2.

Для parseUnknownIobj должно быть примерно то же самое, но если есть какая-то специфика - заняться ею я смогу только в следующем году. Еще раз прошу прощения у всех, кому этот пример нужен был значительно раньше - реально был ужасно занят.

Ну, и конечно всех с Новым годом!

Отредактировано uux (31.12.2007 15:49)

Неактивен

0    0    #7
21.01.2008 21:03

uux
Участник (+836, -80)
Откуда: Москва
Зарегистрирован: 02.12.2006
Сообщений: 1584

Re: Универсальный spellchecker для RTADS

Flint, спасибо на добром слове. Мое решение - скорее демка (косяки из него я не вылавливал). К сожалению, занятия спеллчекером мне придется отложить как минимум до февраля (так же, как участие в ifwiki.ru) - риал-лайф, сволочь, навалился... Чего говорить - в КРИЛовские игры едва успеваю играть:(

Неактивен

Powered by PunBB
© copyright 2001–2021 iFiction.Ru