From cfa936b48dfb9a8bb850ff0d3600f8860345db4a Mon Sep 17 00:00:00 2001 From: mataes2007 Date: Sat, 19 Nov 2011 19:14:29 +0000 Subject: added Boltun git-svn-id: http://miranda-plugins.googlecode.com/svn/trunk@181 e753b5eb-9565-29b2-b5c5-2cc6f99dfbcb --- Boltun/Engine/TalkEngine.cpp | 609 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 609 insertions(+) create mode 100644 Boltun/Engine/TalkEngine.cpp (limited to 'Boltun/Engine/TalkEngine.cpp') diff --git a/Boltun/Engine/TalkEngine.cpp b/Boltun/Engine/TalkEngine.cpp new file mode 100644 index 0000000..a9625df --- /dev/null +++ b/Boltun/Engine/TalkEngine.cpp @@ -0,0 +1,609 @@ +//*********************************************************** +// Copyright © 2008 Valentin Pavlyuchenko +// +// This file is part of Boltun. +// +// Boltun is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// Boltun is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Boltun. If not, see . +// +//*********************************************************** + +#include "TalkEngine.h" +#include +#include + +#ifdef _DEBUG + +//#define DEBUG_PREFIXES +//#define DEBUG_SHOW_LEVEL +//#define DEBUG_SHOW_VARIANTS +//#define DEBUG_SHOW_SOLUTION_REASON + +#endif + +//Enabling next define will make a bot more stupid: +//#define EXCLUDE_SPECIAL_WORDS + +#ifdef DEBUG_SHOW_VARIANTS +extern void AddBotMessage(tstring s); +#endif + +using namespace std; + +void TalkBot::UpdateStartChar(tstring& str) +{ + if (!makeLowercase) + return; + size_t l = str.length(); + if (l) + { + //Answers starting with ' ' must remain unchanged. + if (str[0] == _T(' ')) + { + str = str.substr(1); + return; + } + for (size_t i = 0; i < l; i++) + { + TCHAR cl = (TCHAR)CharLower((LPTSTR)(void*)(long)str[i]); + TCHAR cu = (TCHAR)CharUpper((LPTSTR)(void*)(long)str[i]); + if (i != l - 1) + { + //Do not react to BLONDE ANSWERS + TCHAR ncl = (TCHAR)CharLower((LPTSTR)(void*)(long)str[i + 1]); + TCHAR ncu = (TCHAR)CharUpper((LPTSTR)(void*)(long)str[i + 1]); + if (ncl != ncu && str[i + 1] == ncu) + break; + } + if (cl != cu) + { + str[i] = cl; + break; + } + } + } +} + +TalkBot::TalkBot(const Mind& goodMind) + :mind(goodMind), beSilent(false), makeLowercase(false), + understandAlways(false) +{ + contactDatas = new PerContactData(mind); +} + +TalkBot::~TalkBot() +{ + delete contactDatas; +} + +tstring TalkBot::GetInitMessage(void* contact) +{ + ContactData* d = contactDatas->GetData(contact); + tstring s = d->initial.GetString(); + contactDatas->PutData(contact); + return s; +} + +tstring TalkBot::ReplaceAliases(const tstring &message) +{ + const TCHAR dividers[] = _T(" \t\n\r,./?\\|;:'\"~!#^&*()_-+=[{]}—\1"); + tstring sentence = message; + tstring result; + int len = sentence.length(); + vector words; + map sm; + //Find smiles + for (size_t i = 0; i < sentence.length() - 1; i++) + { + unsigned max = sentence.length() - i; + if (max > mind.GetData()->maxSmileLen) + max = mind.GetData()->maxSmileLen; + for (unsigned j = max; j > 0; j--) + { + tstring item = sentence.substr(i, j); + if (mind.GetData()->smiles.find(item) + != mind.GetData()->smiles.end()) + { + sm[i] = item; + sentence.replace(i, j, _T("\1")); + break; + } + } + } + len = sentence.length(); + bool hadQuestionSigns = false; + int it = 0; + while (it != len) + { + while (it != len && _tcschr(dividers, sentence[it])) + { + if (sentence[it] == _T('?')) + hadQuestionSigns = true; + map::iterator smit; + if (sentence[it] == '\1') + { + smit = sm.find(it); + result.append((*smit).second); + } + else + result.push_back(sentence[it]); + it++; + } + if (it == len) + break; + int start = it; + while (true) + { + while (it != len && !_tcschr(dividers, sentence[it])) + it++; + if (it == len || sentence[it] != _T('-')) + break; + //If we have-a-word-with-minus, we shouldn't split it + if (_tcschr(dividers, sentence[it + 1])) + break; + it += 2; + } + tstring str = sentence.substr(start, it - start); + map::const_iterator al = mind.GetData()->aliases.find(str); + if (al != mind.GetData()->aliases.end()) + result.append((*al).second); + else + result.append(str); + } + return result; +} + +tstring TalkBot::AllReplies(const tstring &incomingMessage, ContactData* contactData, Level &maxValue, std::multimap &mm) +{ + tstring res; + //Part 1 + if (FindExact(contactData, incomingMessage, mind.GetData()->widelyUsed, res)) //widelyUsed + { + return res; + } + //Part 2 + if (FindExact(contactData, incomingMessage, mind.GetData()->study, res)) //study + { +#ifdef DEBUG_PREFIXES + mm.insert(make_pair(LOOKSLIKE, _T("(study_all) ")+res)); +#else + mm.insert(make_pair(LOOKSLIKE, res)); +#endif + maxValue = LOOKSLIKE; + } + //Part 3 + vector sentences; + SplitSectences(incomingMessage, sentences); + ValueChooser<> ch(sentences, true); //Using random order of sentences. + while ((res = ch.GetString()) != _T("")) + { + //Part 4 + if (FindExact(contactData, res, mind.GetData()->widelyUsed, res)) //widelyUsed + { +#ifdef DEBUG_PREFIXES + mm.insert(make_pair(BEST, _T("(widelyused_sent) ")+res)); +#else + mm.insert(make_pair(BEST, res)); +#endif + if (maxValue > BEST) + maxValue = BEST; + } + //Part 5 + if (FindExact(contactData, res, mind.GetData()->study, res)) //study + { +#ifdef DEBUG_PREFIXES + mm.insert(make_pair(LOOKSLIKE, _T("(study_sent) ")+res)); +#else + mm.insert(make_pair(LOOKSLIKE, res)); +#endif + if (maxValue > LOOKSLIKE) + maxValue = LOOKSLIKE; + } + //Part 6 + vector keywords, otherwords; + bool isQuestion; + SplitAndSortWords(res, keywords, otherwords, isQuestion); + //Part 7, 8 + res = _T(""); + FindByKeywords(contactData, keywords, res/*, ures*/, isQuestion); //keywords + if (res != _T("")) + { +#ifdef DEBUG_PREFIXES + mm.insert(make_pair(LOOKSLIKE, _T("(keywords) ")+res)); +#else + mm.insert(make_pair(LOOKSLIKE, res)); +#endif + if (maxValue > LOOKSLIKE) + maxValue = LOOKSLIKE; + } +/* if (ures != _T("")) + { +#ifdef DEBUG_PREFIXES + mm.insert(make_pair(LOOKSLIKE2, _T("(keywords_unstrict) ")+ures)); +#else + mm.insert(make_pair(LOOKSLIKE2, ures)); +#endif + if (maxValue > LOOKSLIKE2) + maxValue = LOOKSLIKE2; + }*/ + //Part 9 + if (FindByOthers(contactData, otherwords, res, isQuestion)) //specialEscapes + { +#ifdef DEBUG_PREFIXES + mm.insert(make_pair(BAD, _T("(otherwords) ")+res)); +#else + mm.insert(make_pair(BAD, res)); +#endif + if (maxValue > BAD) + maxValue = BAD; + } + } + if (!beSilent) + { + //Part 10 + if (FindAny(contactData->escape, res)) //escape + { +#ifdef DEBUG_PREFIXES + mm.insert(make_pair(FAIL, _T("(escape) ") + res)); +#else + mm.insert(make_pair(FAIL, res)); +#endif + if (maxValue > FAIL) + maxValue = FAIL; + } + //Part 11 + if (!understandAlways && FindAny(contactData->failure, res)) //failure + { +#ifdef DEBUG_PREFIXES + mm.insert(make_pair(FAIL, _T("(failure) ") + res)); +#else + mm.insert(make_pair(FAIL, res)); +#endif + if (maxValue > FAIL) + maxValue = FAIL; + } + } + return tstring(); +} + +TalkBot::MessageInfo* TalkBot::Reply(void* contact, tstring incomingMessage, bool saveChoice) +{ + TCHAR* str = new TCHAR[incomingMessage.length()+1]; + _tcscpy(str, incomingMessage.c_str()); + CharLower(str); + incomingMessage = str; + delete str; + ContactData* contactData = contactDatas->GetData(contact); + + if (incomingMessage == contactData->lastMessage && GetTickCount() < contactData->lastMessageTime + 30*60*1000) + { + MessageInfo *info; + //only 2-3 repeats + if (contactData->repeatCount < 2 || contactData->repeatCount == 2 && (rand() % 2)) + { + const vector& v = mind.GetData()->repeats; + tstring res = v[rand() % v.size()]; +#ifdef DEBUG_PREFIXES + info = new MessageInfo(incomingMessage, _T("(repeat_norm) ") + res); +#else + info = new MessageInfo(incomingMessage, res); +#endif + } + else +#ifdef DEBUG_PREFIXES + info = new MessageInfo(incomingMessage, _T("(repeat_silence)")); +#else + info = new MessageInfo(incomingMessage, _T("")); +#endif + if (saveChoice) + RecordAnswer(contactData, *info); + contactDatas->PutData(contact); + return info; + } + + multimap mm; + Level maxValue = NOTHING; + + tstring res = AllReplies(incomingMessage, contactData, maxValue, mm); + if (!res.empty()) + { + UpdateStartChar(res); +#ifdef DEBUG_PREFIXES + MessageInfo *info = new MessageInfo(incomingMessage, _T("(widelyused_all) ") + res); +#else + MessageInfo *info = new MessageInfo(incomingMessage, res); +#endif + if (saveChoice) + RecordAnswer(contactData, *info); + contactDatas->PutData(contact); + return info; + } + + incomingMessage = ReplaceAliases(incomingMessage); + + res = AllReplies(incomingMessage, contactData, maxValue, mm); + if (!res.empty()) + { + UpdateStartChar(res); +#ifdef DEBUG_PREFIXES + MessageInfo *info = new MessageInfo(incomingMessage, _T("(widelyused_all) ") + res); +#else + MessageInfo *info = new MessageInfo(incomingMessage, res); +#endif + if (saveChoice) + RecordAnswer(contactData, *info); + contactDatas->PutData(contact); + return info; + } + + //Also does Part 12 + tstring final = ChooseResult(contactData, maxValue, mm); + MessageInfo *info = new MessageInfo(incomingMessage, final); + UpdateStartChar(final); + if (saveChoice) + RecordAnswer(contactData, *info); + contactDatas->PutData(contact); + return info; +} + +bool TalkBot::FindExact(ContactData* contactData, const tstring &incomingMessage, + const multimap& map, tstring& res) +{ + int max = map.count(incomingMessage); + if (!max) + { + TCHAR c = incomingMessage[incomingMessage.length() - 1]; + if (c != _T('?') && c != _T('.') && c != _T('!')) + return FindExact(contactData, incomingMessage + _T('.'), map, res); + return false; + } + pair range = map.equal_range(incomingMessage); + for (mm_cit it = range.first; it != range.second; it++) + contactData->chooser.AddChoice((*it).second); + res = contactData->chooser.Choose(); + return true; +} + +void TalkBot::AnswerGiven(void* contact, const TalkBot::MessageInfo& info) +{ + ContactData* contactData = contactDatas->GetData(contact); + RecordAnswer(contactData, info); + contactDatas->PutData(contact); +} + +void TalkBot::RecordAnswer(ContactData *contactData, const TalkBot::MessageInfo& info) +{ + contactData->chooser.SaveChoice(info.Answer); + if (contactData->lastMessage == info.Question) + contactData->repeatCount++; + else + contactData->repeatCount = 0; + contactData->lastMessageTime = GetTickCount(); + contactData->lastMessage = info.Question; +} + +bool TalkBot::FindAny(ValueChooser<> &ch, tstring& res) +{ + if (!ch.GetContainer().size()) + return false; + res = ch.GetString(); + return true; +} + +void TalkBot::SplitSectences(const tstring &incomingMessage, vector& vec) +{ + //FIXME: (THINK ABOUT IT:-) )these chars not always mark the end of sentence. + const TCHAR symbols[] = _T(".?!"); + int it = 0, len = incomingMessage.length(); + while (it != len) + { + while (it != len && _istspace(incomingMessage[it])) + it++; + int start = it; + while (it != len) + { + if (_tcschr(symbols, incomingMessage[it++])) + { + //Test for a :-! smile + if (it > 2 && incomingMessage[it-1] == _T('!') + && incomingMessage[it-2] == _T('-') + && incomingMessage[it-3] == _T(':')) + continue; + while (it != len && _tcschr(symbols, incomingMessage[it])) + it++; + break; + } + } + vec.insert(vec.end(), incomingMessage.substr(start, it - start)); + } +} + +#ifdef _DEBUG +tstring LevelToStr(TalkBot::Level target) +{ + tstring lev; + switch (target) + { + case TalkBot::BEST: lev = _T("BEST(0)"); break; + case TalkBot::LOOKSLIKE: lev = _T("LOOKSLIKE(1)"); break; + case TalkBot::BAD: lev = _T("BAD(2)"); break; + case TalkBot::FAIL: lev = _T("FAIL(3)"); break; + case TalkBot::NOTHING: lev = _T("NOTHING(4)"); break; + } + return lev; +} +#endif + +tstring TalkBot::ChooseResult(ContactData* contactData, Level maxValue, const multimap &mm) +{ +#ifdef DEBUG_SHOW_VARIANTS + AddBotMessage(_T(">>Availabe:")); + for (multimap::iterator it = mm.begin(); it != mm.end(); it++) + AddBotMessage(LevelToStr((*it).first) + _T(": ") + (*it).second); + AddBotMessage(_T(">>Result:")); +#endif + if (maxValue == NOTHING) + return _T(""); + Level target = maxValue; + int num = mm.count(target); +/* if (!num) + { + target = maxValue; + num = mm.count(target); + }*/ + typedef multimap::const_iterator lt_cit; + pair range = mm.equal_range(target); + for (lt_cit it = range.first; it != range.second; it++) + contactData->chooser.AddChoice((*it).second); +#ifdef DEBUG_SHOW_LEVEL + tstring lev = LevelToStr(target); + return lev + _T(": ") + contactData->chooser.Choose(); +#else + return contactData->chooser.Choose(); +#endif +} + +void TalkBot::FindByKeywords(ContactData* contactData, const vector &keywords, tstring& res/*, tstring& ures*/, + bool isQuestion) +{ + if (keywords.size() == 0) + return; + const multimap &keys = isQuestion ? mind.GetData()->qkeywords : + mind.GetData()->keywords; + for (multimap::const_iterator it = keys.begin(); it != keys.end(); it++) + { + float prio; + if ((*it).first.MatchesAll(keywords/*, strict*/, prio)) +#ifdef DEBUG_SHOW_SOLUTION_REASON + contactData->chooser.AddChoice((tstring)(*it).first + _T(": - ") + (*it).second, prio); +#else + contactData->chooser.AddChoice((*it).second, prio); +#endif + } + res = contactData->chooser.Choose(); +} + +bool TalkBot::FindByOthers(ContactData* contactData, const vector &otherwords, tstring& res, bool isQuestion) +{ + //vector results; + const multimap &specs = isQuestion ? mind.GetData()->qspecialEscapes : + mind.GetData()->specialEscapes; + for (multimap::const_iterator it = specs.begin(); + it != specs.end(); it++) + if ((*it).first.MatchesAny(otherwords)) + { +#ifdef DEBUG_SHOW_SOLUTION_REASON + contactData->chooser.AddChoice((tstring)(*it).first + _T(": - ") + (*it).second); +#else + contactData->chooser.AddChoice((*it).second); +#endif + } + res = contactData->chooser.Choose(); + if (res.empty()) + return false; + return true; +} + +const Mind& TalkBot::GetMind() const +{ + return mind; +} + +void TalkBot::SplitAndSortWords(tstring sentence, vector& keywords, + vector& otherwords, bool& isQuestion) +{ + const TCHAR dividers[] = _T(" \t\n\r,./?\\|;:'\"~!#^&*()_-+=[{]}—"); + int len = sentence.length(); + vector words; + map sm; + //Find smiles + for (size_t i = 0; i < sentence.length() - 1; i++) + { + unsigned max = sentence.length() - i; + if (max > mind.GetData()->maxSmileLen) + max = mind.GetData()->maxSmileLen; + for (unsigned j = max; j > 0; j--) + { + tstring item = sentence.substr(i, j); + if (mind.GetData()->smiles.find(item) + != mind.GetData()->smiles.end()) + { + sm[i] = item; + sentence.replace(i, j, _T(" ")); + break; + } + } + } + len = sentence.length(); + bool hadQuestionSigns = false; + int it = 0; + while (it != len) + { + while (it != len && _tcschr(dividers, sentence[it])) + { + if (sentence[it] == _T('?')) + hadQuestionSigns = true; + map::iterator smit; + if (_istspace(sentence[it]) && (smit = sm.find(it)) != sm.end()) + words.push_back((*smit).second); + it++; + } + if (it == len) + break; + hadQuestionSigns = false; + int start = it; + while (true) + { + while (it != len && !_tcschr(dividers, sentence[it])) + it++; + if (it == len || sentence[it] != _T('-')) + break; + //If we have-a-word-with-minus, we shouldn't split it + if (_tcschr(dividers, sentence[it + 1])) + break; + it += 2; + } + tstring str = sentence.substr(start, it - start); + words.push_back(str); + } + isQuestion = hadQuestionSigns; + for (vector::iterator it = words.begin(); it != words.end(); it++) + { + if (!isQuestion) + { + if (mind.GetData()->question.find(*it) != mind.GetData()->question.end()) + isQuestion = true; + } + if (mind.GetData()->special.find(*it) != mind.GetData()->special.end()) + otherwords.push_back(*it); +#ifdef EXCLUDE_SPECIAL_WORDS + else +#endif + keywords.push_back(*it); + } +} + +void TalkBot::SetSilent(const bool isSilent) +{ + beSilent = isSilent; +} + +void TalkBot::SetLowercase(const bool isLowercase) +{ + makeLowercase = isLowercase; +} + +void TalkBot::SetUnderstandAlways(const bool understandAlways) +{ + this->understandAlways = understandAlways; +} \ No newline at end of file -- cgit v1.2.3