File indexing completed on 2024-04-28 07:29:04

0001 /***************************************************************************
0002  *   Copyright (C) 2005 by Joshua Keel <joshuakeel@gmail.com>              *
0003  *             (C) 2007-2021 by Jeremy Whiting <jpwhiting@kde.org>         *
0004  *             (C) 2012 by Laszlo Papp <lpapp@kde.org>                     *
0005  *                                                                         *
0006  *   Portions of this code taken from KMessedWords by Reuben Sutton        *
0007  *                                                                         *
0008  *   This program is free software; you can redistribute it and/or modify  *
0009  *   it under the terms of the GNU General Public License as published by  *
0010  *   the Free Software Foundation; either version 2 of the License, or     *
0011  *   (at your option) any later version.                                   *
0012  *                                                                         *
0013  *   This program is distributed in the hope that it will be useful,       *
0014  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0015  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0016  *   GNU General Public License for more details.                          *
0017  *                                                                         *
0018  *   You should have received a copy of the GNU General Public License     *
0019  *   along with this program; if not, write to the                         *
0020  *   Free Software Foundation, Inc.,                                       *
0021  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
0022  ***************************************************************************/
0023 
0024 #include "kanagramgame.h"
0025 
0026 #include "kanagramsettings.h"
0027 
0028 #include <sharedkvtmlfiles.h>
0029 #include <KEduVocDocument>
0030 #include <KEduVocExpression>
0031 #ifdef HAVE_SPEECH
0032 #include <QTextToSpeech>
0033 #endif
0034 
0035 #include <KLocalizedString>
0036 #include <sonnet/speller.h>
0037 
0038 #include <QLocale>
0039 #include <QFileInfo>
0040 #include <QRandomGenerator>
0041 #include <QStandardPaths>
0042 
0043 KanagramGame::KanagramGame()
0044     : m_fileIndex(-1)
0045       ,m_document(nullptr)
0046 #ifdef HAVE_SPEECH
0047       ,m_speech(nullptr)
0048 #endif
0049       ,m_totalScore(0)
0050       ,m_totalScore2(0)
0051       ,m_currentPlayerNumber(1)
0052       ,m_speller(nullptr)
0053 {
0054     loadSettings();
0055 
0056     // Get the list of vocabularies
0057     refreshVocabularyList();
0058 
0059     // Load the default vocabulary
0060     loadDefaultVocabulary();
0061 #ifdef HAVE_SPEECH
0062     m_speech = new QTextToSpeech(this);
0063 #endif
0064 
0065     m_speller = new Sonnet::Speller();
0066     m_speller->setLanguage(sanitizedDataLanguage());
0067 }
0068 
0069 KanagramGame::~KanagramGame()
0070 {
0071     // Save any settings that may have changed
0072     KanagramSettings::self()->save();
0073 
0074     delete m_document;
0075     m_document = nullptr;
0076     delete m_speller;
0077     m_speller = nullptr;
0078 #ifdef HAVE_SPEECH
0079     delete m_speech;
0080     m_speech = nullptr;
0081 #endif
0082 }
0083 
0084 bool KanagramGame::checkFile()
0085 {
0086     if (!QFile::exists(m_filename) && !QFile::exists(QStandardPaths::locate(QStandardPaths::GenericDataLocation, m_filename)))
0087     {
0088         Q_EMIT fileError(m_filename);
0089         return false;
0090     }
0091 
0092     return true;
0093 }
0094 
0095 QString KanagramGame::sanitizedDataLanguage() const
0096 {
0097     QString dataLanguage = KanagramSettings::dataLanguage();
0098     QStringList languageCodes = SharedKvtmlFiles::languages();
0099 
0100     if (dataLanguage.isEmpty() || !languageCodes.contains(dataLanguage)) {
0101         if (languageCodes.contains(QLocale::system().uiLanguages().at(0))) {
0102             dataLanguage = QLocale::system().uiLanguages().at(0);
0103         } else {
0104             dataLanguage = QStringLiteral("en");
0105         }
0106 
0107     }
0108 
0109     return dataLanguage;
0110 
0111 }
0112 
0113 void KanagramGame::loadDefaultVocabulary()
0114 {
0115     int index = KanagramSettings::currentVocabulary();
0116     if (index == -1)
0117         index = 0;
0118     useVocabulary(index);
0119     nextAnagram();
0120 }
0121 
0122 void KanagramGame::setSinglePlayerMode(bool singlePlayer)
0123 {
0124     KanagramSettings::setSinglePlayerMode(singlePlayer);
0125     Q_EMIT singlePlayerChanged();
0126 }
0127 
0128 bool KanagramGame::singlePlayerMode()
0129 {
0130   return KanagramSettings::singlePlayerMode();
0131 }
0132 
0133 int KanagramGame::getPlayerNumber()
0134 {
0135     return m_currentPlayerNumber;
0136 }
0137 
0138 void KanagramGame::setPlayerNumber(int pnumber)
0139 {
0140     m_currentPlayerNumber = pnumber;
0141     Q_EMIT currentPlayerChanged();
0142 }
0143 
0144 bool KanagramGame::refreshVocabularyList()
0145 {
0146     QString oldFilename = m_filename;
0147     m_fileList = SharedKvtmlFiles::fileNames(sanitizedDataLanguage());
0148     if ( m_document ) {
0149         useVocabulary(m_document->title());
0150     }
0151     return oldFilename != m_filename;
0152 }
0153 
0154 QStringList KanagramGame::vocabularyList() const
0155 {
0156     return SharedKvtmlFiles::titles(sanitizedDataLanguage());
0157 }
0158 
0159 void KanagramGame::useVocabulary(const QString &vocabularyname)
0160 {
0161     useVocabulary(vocabularyList().indexOf(vocabularyname));
0162 }
0163 
0164 void KanagramGame::useVocabulary(int index)
0165 {
0166     int previous = m_fileIndex;
0167     if (index < 0 && m_fileList.size() > 0)
0168     {
0169         // Use the last
0170         index = m_fileList.size() - 1;
0171     }
0172     else if (index >= m_fileList.size())
0173     {
0174         index = 0;
0175     }
0176 
0177     m_fileIndex = index;
0178     m_filename = m_fileList.size() > index  && index >= 0 ? m_fileList.at(index) : QString();
0179 
0180     if (m_fileIndex != previous && checkFile()) {
0181         delete m_document;
0182         m_document = new KEduVocDocument(this);
0183         ///@todo open returns KEduVocDocument::ErrorCode
0184         m_document->open(QUrl::fromLocalFile(m_filename), KEduVocDocument::FileIgnoreLock);
0185         m_answeredWords.clear();
0186         // Save the setting
0187         KanagramSettings::setCurrentVocabulary(index);
0188         KanagramSettings::self()->save();
0189         Q_EMIT titleChanged();
0190     }
0191 }
0192 
0193 void KanagramGame::previousVocabulary()
0194 {
0195     useVocabulary(m_fileIndex - 1);
0196 }
0197 
0198 void KanagramGame::nextVocabulary()
0199 {
0200     useVocabulary(m_fileIndex + 1);
0201 }
0202 
0203 void KanagramGame::nextAnagram()
0204 {
0205     if (checkFile())
0206     {
0207         int totalWords = m_document->lesson()->entryCount(KEduVocLesson::Recursive);
0208         int randomWordIndex = QRandomGenerator::global()->bounded(totalWords);
0209 
0210         if (totalWords == (int)m_answeredWords.size())
0211         {
0212             m_answeredWords.clear();
0213         }
0214 
0215         if (totalWords > 0)
0216         {
0217             KEduVocTranslation *translation = m_document->lesson()->entries(KEduVocLesson::Recursive).at(randomWordIndex)->translation(0);
0218 
0219             // Find the next word not used yet
0220             while (m_answeredWords.contains(translation->text()))
0221             {
0222                 randomWordIndex = QRandomGenerator::global()->bounded(totalWords);
0223                 translation =  m_document->lesson()->entries(KEduVocLesson::Recursive).at(randomWordIndex)->translation(0);
0224             }
0225 
0226             // Make case consistent so german words that start capitalized will not
0227             // be so easy to guess
0228             if (KanagramSettings::uppercaseOnly())
0229             {
0230                m_originalWord = translation->text().toUpper();
0231             }
0232             else
0233             {
0234                m_originalWord = translation->text().toLower();
0235             }
0236             m_picHintUrl = translation->imageUrl();
0237             m_audioUrl = translation->soundUrl();
0238 
0239             m_answeredWords.append(m_originalWord);
0240             createAnagram();
0241             m_hint = translation->comment();
0242 
0243             if (m_hint.isEmpty())
0244             {
0245                 m_hint = i18n("No hint");
0246             }
0247         }
0248         else
0249         {
0250             // this file has no entries
0251             m_originalWord = QLatin1String("");
0252             m_hint = QLatin1String("");
0253             m_picHintUrl = QUrl();
0254             m_audioUrl = QUrl();
0255             // TODO: add some error reporting here
0256         }
0257         Q_EMIT userAnswerChanged();
0258         Q_EMIT wordChanged();
0259     }
0260 }
0261 
0262 QString KanagramGame::filename() const
0263 {
0264     return m_fileList.isEmpty() ? m_filename : m_fileList.at(m_fileIndex);
0265 }
0266 
0267 QStringList KanagramGame::anagram() const
0268 {
0269     QStringList resultList;
0270     for (const QChar &userLetter : qAsConst(m_anagram))
0271     {
0272         resultList.append(userLetter);
0273     }
0274 
0275     return resultList;
0276 }
0277 
0278 QString KanagramGame::hint() const
0279 {
0280     return m_hint;
0281 }
0282 
0283 QString KanagramGame::word() const
0284 {
0285     return m_originalWord;
0286 }
0287 
0288 QStringList KanagramGame::userAnswer() const
0289 {
0290     QStringList returnList;
0291     for (const QChar &letter : qAsConst(m_userAnswer))
0292     {
0293         returnList.append(letter);
0294     }
0295     return returnList;
0296 }
0297 
0298 
0299 void KanagramGame::createAnagram()
0300 {
0301     if (m_originalWord.count(m_originalWord.at(0)) < m_originalWord.size()) {
0302         QString anagram;
0303         QString letters;
0304         int randomIndex;
0305 
0306         do {
0307             anagram.clear();
0308             letters = m_originalWord;
0309             while (!letters.isEmpty())
0310             {
0311                 randomIndex = QRandomGenerator::global()->bounded(letters.length());
0312                 anagram.append(letters.at(randomIndex));
0313                 letters.remove(randomIndex, 1);
0314             }
0315         } while (anagram == m_originalWord);
0316 
0317         m_anagram = anagram;
0318         m_userAnswer.clear();
0319     } else {
0320         m_anagram = m_originalWord;
0321         m_userAnswer.clear();
0322     }
0323 }
0324 
0325 bool KanagramGame::useSounds()
0326 {
0327     return KanagramSettings::useSounds();
0328 }
0329 
0330 QString KanagramGame::documentTitle() const
0331 {
0332     if (m_document)
0333     {
0334         return m_document->title();
0335     }
0336 
0337     return QString();
0338 }
0339 
0340 QStringList KanagramGame::languageNames()
0341 {
0342     const QStringList languageCodes = SharedKvtmlFiles::languages();
0343     if (languageCodes.isEmpty()) {
0344         return QStringList();
0345     }
0346 
0347     QStringList languageNames;
0348 
0349     // Get the language names from the language codes
0350     KConfig entry(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("locale/") + "all_languages"));
0351 
0352     for (const QString& languageCode : languageCodes)
0353     {
0354         KConfigGroup group = entry.group(languageCode);
0355 
0356         QString languageName = group.readEntry("Name");
0357         if (languageName.isEmpty())
0358         {
0359             languageName = i18nc("@item:inlistbox no language for that locale", "None");
0360         }
0361 
0362         languageNames.append(languageName);
0363         m_languageCodeNameHash.insert(languageCode, languageName);
0364     }
0365 
0366     std::sort(languageNames.begin(), languageNames.end());
0367     return languageNames;
0368 }
0369 
0370 QString KanagramGame::dataLanguage() const
0371 {
0372     return QLocale::languageToString(QLocale(sanitizedDataLanguage()).language());
0373 }
0374 
0375 void KanagramGame::setDataLanguage(const QString& dataLanguage)
0376 {
0377     KanagramSettings::setDataLanguage(m_languageCodeNameHash.key(dataLanguage));
0378     KanagramSettings::self()->save();
0379     // Update the speller's language accordingly
0380     m_speller->setLanguage(sanitizedDataLanguage());
0381     Q_EMIT dataLanguageChanged();
0382 }
0383 
0384 QUrl KanagramGame::picHint()
0385 {
0386     return m_picHintUrl;
0387 }
0388 
0389 QUrl KanagramGame::audioFile()
0390 {
0391     return m_audioUrl;
0392 }
0393 
0394 #ifdef HAVE_SPEECH
0395 void KanagramGame::wordRevealed()
0396 {
0397     if (KanagramSettings::enablePronunciation())
0398     {
0399         say(m_originalWord);
0400     }
0401 }
0402 
0403 void KanagramGame::say(const QString &text)
0404 {
0405     if ( text.isEmpty() )
0406         return;
0407 
0408     if ( m_speech )
0409     {
0410         m_speech->say(text);
0411     }
0412 }
0413 #endif
0414 
0415 int KanagramGame::hintHideTime()
0416 {
0417     QString hideTimeString = KanagramSettings::hintHideTime();
0418 
0419     int hintHideTime = getNumericSetting(hideTimeString);
0420     if (hintHideTime)
0421         return ((hintHideTime * 2) + 1);
0422     else
0423         return 0;
0424 }
0425 
0426 int KanagramGame::resolveTime()
0427 {
0428     return KanagramSettings::resolveTime().toInt();
0429 }
0430 
0431 int KanagramGame::scoreTime()
0432 {
0433     QString scoreTimeString = KanagramSettings::scoreTime();
0434 
0435     int scoreTime = getNumericSetting(scoreTimeString);
0436     return ((scoreTime + 1) * 15);
0437 }
0438 
0439 void KanagramGame::moveLetterToUserAnswer(int position)
0440 {
0441     m_userAnswer.append(m_anagram[position]);
0442     m_anagram.remove(position, 1);
0443     Q_EMIT wordChanged();
0444     Q_EMIT userAnswerChanged();
0445 }
0446 
0447 void KanagramGame::moveLetterToAnagram(int position)
0448 {
0449     m_anagram.append(m_userAnswer[position]);
0450     m_userAnswer.remove(position, 1);
0451     Q_EMIT wordChanged();
0452     Q_EMIT userAnswerChanged();
0453 }
0454 
0455 void KanagramGame::resetAnagram()
0456 {
0457     m_anagram = m_userAnswer;
0458     m_userAnswer.clear();
0459     Q_EMIT wordChanged();
0460     Q_EMIT userAnswerChanged();
0461 }
0462 
0463 void KanagramGame::moveLetter(const QString &letter)
0464 {
0465     QString small = letter.toLower();
0466     QString strippedAnagram = stripAccents(m_anagram);
0467     int index = m_anagram.toLower().indexOf(small);
0468     if (index != -1)
0469     {
0470         moveLetterToUserAnswer(index);
0471     }
0472     else
0473     {
0474         index = strippedAnagram.toLower().indexOf(small);
0475         if (index != -1)
0476         {
0477              moveLetterToUserAnswer(index);
0478         }
0479         else
0480         {
0481             QString strippedAnswer = stripAccents(m_userAnswer);
0482             index = m_userAnswer.toLower().indexOf(small);
0483             if (index != -1)
0484             {
0485                 moveLetterToAnagram(index);
0486             }
0487             else
0488             {
0489                 index = strippedAnswer.toLower().indexOf(small);
0490                 if (index != -1)
0491                 {
0492                     moveLetterToAnagram(index);
0493                 }
0494             }
0495         }
0496     }
0497 }
0498 
0499 int KanagramGame::getNumericSetting(const QString &settingString)
0500 {
0501     int indexFound_setting = settingString.size();
0502     for (int k = 0; k < indexFound_setting; ++k)
0503     {
0504         if (!settingString.at(k).isDigit())
0505         {
0506             indexFound_setting = k;
0507             break;
0508         }
0509     }
0510 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0511     return settingString.leftRef(indexFound_setting).toInt();
0512 #else
0513     return QStringView(settingString).left(indexFound_setting).toInt();
0514 #endif
0515 }
0516 
0517 void KanagramGame::resetTotalScore()
0518 {
0519     if (m_currentPlayerNumber == 1)
0520     {
0521         m_totalScore = 0;
0522         m_totalScore2 = 0;
0523         Q_EMIT scoreChanged();
0524     }
0525 }
0526 
0527 void KanagramGame::adjustScore(int points)
0528 {
0529     if (m_currentPlayerNumber == 1)
0530         m_totalScore += points;
0531     else
0532         m_totalScore2 += points;
0533     Q_EMIT scoreChanged();
0534 }
0535 
0536 int KanagramGame::totalScore()
0537 {
0538     return m_totalScore;
0539 }
0540 
0541 int KanagramGame::totalScore2()
0542 {
0543     return m_totalScore2;
0544 }
0545 
0546 void KanagramGame::revealWord()
0547 {
0548     m_anagram = m_originalWord;
0549     Q_EMIT wordChanged();
0550 }
0551 
0552 bool KanagramGame::checkWord()
0553 {
0554     QString enteredWord = m_userAnswer.toLower().trimmed();
0555     QString lowerOriginal = m_originalWord.toLower();
0556     QString strippedOriginal = stripAccents(m_originalWord);
0557     if (!enteredWord.isEmpty())
0558     {
0559         if (enteredWord == lowerOriginal ||
0560             stripAccents(enteredWord) == strippedOriginal ||
0561             (m_speller->isCorrect(enteredWord) && m_speller->isValid() &&
0562                (isAnagram(enteredWord, lowerOriginal) ||
0563                 isAnagram(enteredWord, strippedOriginal))))
0564         {
0565 #ifdef HAVE_SPEECH
0566             if (KanagramSettings::enablePronunciation())
0567             {
0568                 // User wants words spoken
0569                 say(m_originalWord);
0570             }
0571 #endif
0572             return true;
0573         }
0574         else
0575         {
0576             return false;
0577         }
0578     }
0579     else
0580     {
0581         return false;
0582     }
0583 }
0584 
0585 bool KanagramGame::isAnagram(const QString& enteredword, const QString& word)
0586 {
0587     QString test = word;
0588     if (enteredword.length() <= word.length())
0589     {
0590         for (int i=0; i < enteredword.length(); i++)
0591         {
0592             int found = test.indexOf(enteredword[i]);
0593 
0594             if (found != -1)
0595             {
0596                 test.remove(found, 1);
0597             }
0598             else
0599                 break;
0600         }
0601 
0602         if (test.isEmpty())
0603             return true;
0604         else
0605             return false;
0606     }
0607     else
0608         return false;
0609 }
0610 
0611 QString KanagramGame::stripAccents(const QString& original)
0612 {
0613     QString noAccents;
0614     QString decomposed = original.normalized(QString::NormalizationForm_D);
0615     for (int i = 0; i < decomposed.length(); ++i) {
0616         if ( decomposed[i].category() != QChar::Mark_NonSpacing ) {
0617             noAccents.append(decomposed[i]);
0618         }
0619     }
0620     return noAccents;
0621 }
0622 
0623 void KanagramGame::reloadSettings()
0624 {
0625     loadSettings();
0626     if (refreshVocabularyList())
0627     {
0628         nextVocabulary();
0629         nextAnagram();
0630     }
0631 }
0632 
0633 void KanagramGame::loadSettings()
0634 {
0635     QString correctAnswerScore = KanagramSettings::correctAnswerScore();
0636 
0637     m_correctAnswerScore = getNumericSetting(correctAnswerScore);
0638     m_correctAnswerScore = (m_correctAnswerScore + 1) * 5;
0639 
0640     QString incorrectAnswerScore = KanagramSettings::incorrectAnswerScore();
0641 
0642     m_incorrectAnswerScore = getNumericSetting(incorrectAnswerScore);
0643     m_incorrectAnswerScore = (m_incorrectAnswerScore + 1) * (-1);
0644 
0645     QString revealAnswerScore = KanagramSettings::revealAnswerScore();
0646 
0647     m_revealAnswerScore = getNumericSetting(revealAnswerScore);
0648     m_revealAnswerScore = (m_revealAnswerScore + 1) * (-2);
0649 
0650     QString skippedWordScore = KanagramSettings::skippedWordScore();
0651 
0652     m_skippedWordScore = getNumericSetting(skippedWordScore);
0653     m_skippedWordScore = (m_skippedWordScore + 1) * (-2);
0654 
0655     if (KanagramSettings::dataLanguage().isEmpty())
0656     {
0657         const QStringList userLanguagesCode = QLocale::system().uiLanguages();
0658         QStringList sharedKvtmlFilesLanguages = SharedKvtmlFiles::languages();
0659         QString foundLanguage;
0660         for (const QString &userLanguageCode : userLanguagesCode)
0661         {
0662             if (sharedKvtmlFilesLanguages.contains(userLanguageCode))
0663             {
0664                 foundLanguage = userLanguageCode;
0665                 break;
0666             }
0667         }
0668 
0669         KanagramSettings::setDataLanguage(!foundLanguage.isEmpty() ? foundLanguage : QStringLiteral("en"));
0670     }
0671 }
0672 
0673 void KanagramGame::answerCorrect()
0674 {
0675     if (m_currentPlayerNumber == 1)
0676         m_totalScore += m_correctAnswerScore;
0677     else
0678         m_totalScore2 += m_correctAnswerScore;
0679     Q_EMIT scoreChanged();
0680 }
0681 
0682 void KanagramGame::answerIncorrect()
0683 {
0684     if (m_currentPlayerNumber == 1)
0685         m_totalScore += m_incorrectAnswerScore;
0686     else
0687         m_totalScore2 += m_incorrectAnswerScore;
0688     Q_EMIT scoreChanged();
0689 }
0690 
0691 void KanagramGame::answerRevealed()
0692 {
0693     if (m_currentPlayerNumber == 1)
0694         m_totalScore += m_revealAnswerScore;
0695     else
0696         m_totalScore2 += m_revealAnswerScore;
0697     Q_EMIT scoreChanged();
0698 }
0699 
0700 void KanagramGame::answerSkipped()
0701 {
0702     if (m_currentPlayerNumber == 1)
0703         m_totalScore += m_skippedWordScore;
0704     else
0705         m_totalScore2 += m_skippedWordScore;
0706     Q_EMIT scoreChanged();
0707 }
0708 
0709 #include "moc_kanagramgame.cpp"