File indexing completed on 2025-01-26 03:29:46
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"