Create reward.js
Browse files- js/logic/reward.js +201 -0
js/logic/reward.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class RewardSystem {
|
| 2 |
+
constructor() {
|
| 3 |
+
this.quizBaseReward = 10;
|
| 4 |
+
this.exerciseBaseReward = 20;
|
| 5 |
+
this.lessonCompletionReward = 15;
|
| 6 |
+
this.perfectBonus = 5;
|
| 7 |
+
this.streakBonus = 2;
|
| 8 |
+
this.dailyBonus = 10;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
calculateQuizReward(correctAnswers, totalQuestions, timeSpent = 0, streak = 0) {
|
| 12 |
+
if (totalQuestions === 0) return 0;
|
| 13 |
+
|
| 14 |
+
const accuracy = correctAnswers / totalQuestions;
|
| 15 |
+
let baseScore = accuracy * this.quizBaseReward;
|
| 16 |
+
|
| 17 |
+
// پاداش کامل
|
| 18 |
+
if (correctAnswers === totalQuestions) {
|
| 19 |
+
baseScore += this.perfectBonus;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
// پاداش سرعت (اگر زمان کمتر از حد انتظار باشد)
|
| 23 |
+
const expectedTime = totalQuestions * 30; // 30 ثانیه برای هر سوال
|
| 24 |
+
if (timeSpent > 0 && timeSpent < expectedTime) {
|
| 25 |
+
const timeBonus = ((expectedTime - timeSpent) / expectedTime) * 5;
|
| 26 |
+
baseScore += timeBonus;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// پاداش استریک
|
| 30 |
+
if (streak > 0) {
|
| 31 |
+
baseScore += Math.min(streak * this.streakBonus, 10); // حداکثر 10 امتیاز برای استریک
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
return Math.round(baseScore);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
calculateExerciseReward(isCorrect, keywordMatches, totalKeywords, complexity = 1) {
|
| 38 |
+
if (!isCorrect) return 0;
|
| 39 |
+
|
| 40 |
+
let baseReward = this.exerciseBaseReward * complexity;
|
| 41 |
+
|
| 42 |
+
// پاداش تطابق کلمات کلیدی
|
| 43 |
+
if (totalKeywords > 0) {
|
| 44 |
+
const matchRatio = keywordMatches / totalKeywords;
|
| 45 |
+
const matchBonus = matchRatio * 10; // حداکثر 10 امتیاز برای تطابق کامل
|
| 46 |
+
baseReward += matchBonus;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// پاداش کامل برای تمرینهای پیچیده
|
| 50 |
+
if (complexity >= 2 && keywordMatches === totalKeywords) {
|
| 51 |
+
baseReward += this.perfectBonus;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
return Math.round(baseReward);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
calculateLessonReward(lessonDuration, interactions = 0) {
|
| 58 |
+
const baseReward = this.lessonCompletionReward;
|
| 59 |
+
|
| 60 |
+
// پاداش بر اساس مدت زمان مطالعه
|
| 61 |
+
const timeBonus = Math.min(lessonDuration / 60, 5); // حداکثر 5 امتیاز برای زمان
|
| 62 |
+
|
| 63 |
+
// پاداش تعامل
|
| 64 |
+
const interactionBonus = Math.min(interactions * 0.5, 3); // حداکثر 3 امتیاز برای تعامل
|
| 65 |
+
|
| 66 |
+
return Math.round(baseReward + timeBonus + interactionBonus);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
calculateDailyBonus(consecutiveDays) {
|
| 70 |
+
let bonus = this.dailyBonus;
|
| 71 |
+
|
| 72 |
+
// پاداش برای روزهای متوالی
|
| 73 |
+
if (consecutiveDays >= 7) {
|
| 74 |
+
bonus += 10; // پاداش هفتگی
|
| 75 |
+
}
|
| 76 |
+
if (consecutiveDays >= 30) {
|
| 77 |
+
bonus += 25; // پاداش ماهانه
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
return bonus;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
getAchievementReward(achievementType) {
|
| 84 |
+
const rewards = {
|
| 85 |
+
'first_lesson': 50,
|
| 86 |
+
'quiz_master': 100,
|
| 87 |
+
'exercise_pro': 75,
|
| 88 |
+
'week_streak': 150,
|
| 89 |
+
'month_streak': 300,
|
| 90 |
+
'perfect_score': 50,
|
| 91 |
+
'fast_learner': 60,
|
| 92 |
+
'consistent_learner': 80
|
| 93 |
+
};
|
| 94 |
+
|
| 95 |
+
return rewards[achievementType] || 25;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
getLevelThreshold(level) {
|
| 99 |
+
// فرمول نمایی برای سطوح
|
| 100 |
+
return Math.floor(100 * Math.pow(1.5, level - 1));
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
calculateLevel(totalScore) {
|
| 104 |
+
let level = 1;
|
| 105 |
+
let threshold = this.getLevelThreshold(level);
|
| 106 |
+
|
| 107 |
+
while (totalScore >= threshold) {
|
| 108 |
+
level++;
|
| 109 |
+
threshold = this.getLevelThreshold(level);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
return {
|
| 113 |
+
level: level - 1,
|
| 114 |
+
currentScore: totalScore,
|
| 115 |
+
nextLevelThreshold: threshold,
|
| 116 |
+
progress: this.getLevelThreshold(level - 1) > 0 ?
|
| 117 |
+
((totalScore - this.getLevelThreshold(level - 2)) /
|
| 118 |
+
(threshold - this.getLevelThreshold(level - 2))) * 100 : 0
|
| 119 |
+
};
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
getRewardFeedback(reward, maxPossibleReward, performanceType) {
|
| 123 |
+
const percentage = (reward / maxPossibleReward) * 100;
|
| 124 |
+
|
| 125 |
+
const feedbackTemplates = {
|
| 126 |
+
quiz: {
|
| 127 |
+
excellent: "عالی! شما بر این مبحث مسلط هستید! 🎉",
|
| 128 |
+
good: "خوب! در مسیر درستی قرار دارید! 👍",
|
| 129 |
+
average: "قابل قبول. میتوانید بهتر عمل کنید! 💪",
|
| 130 |
+
needs_improvement: "نیاز به مطالعه بیشتر دارید. ادامه دهید! 📚"
|
| 131 |
+
},
|
| 132 |
+
exercise: {
|
| 133 |
+
excellent: "تمرین فوقالعادهای بود! مهارت شما قابل تحسین است! 🌟",
|
| 134 |
+
good: "تمرین خوبی بود. نکات اصلی را رعایت کردید! ✅",
|
| 135 |
+
average: "تمرین قابل ق��ولی بود. میتوانید جزئیات بیشتری اضافه کنید! 📝",
|
| 136 |
+
needs_improvement: "نیاز به تمرین بیشتر دارید. دوباره تلاش کنید! 🔄"
|
| 137 |
+
},
|
| 138 |
+
lesson: {
|
| 139 |
+
excellent: "مطالعه بسیار موثری داشتید! ادامه دهید! 🚀",
|
| 140 |
+
good: "مطالعه خوبی بود. مفاهیم اصلی را فرا گرفتید! 📖",
|
| 141 |
+
average: "مطالعه قابل قبولی بود. پیشنهاد میکنیم دوباره مرور کنید! 🔍",
|
| 142 |
+
needs_improvement: "نیاز به مطالعه عمیقتر دارید. 💡"
|
| 143 |
+
}
|
| 144 |
+
};
|
| 145 |
+
|
| 146 |
+
const templates = feedbackTemplates[performanceType] || feedbackTemplates.quiz;
|
| 147 |
+
|
| 148 |
+
if (percentage >= 90) {
|
| 149 |
+
return {
|
| 150 |
+
message: templates.excellent,
|
| 151 |
+
emoji: "🎉",
|
| 152 |
+
color: "#38a169"
|
| 153 |
+
};
|
| 154 |
+
} else if (percentage >= 70) {
|
| 155 |
+
return {
|
| 156 |
+
message: templates.good,
|
| 157 |
+
emoji: "👍",
|
| 158 |
+
color: "#319795"
|
| 159 |
+
};
|
| 160 |
+
} else if (percentage >= 50) {
|
| 161 |
+
return {
|
| 162 |
+
message: templates.average,
|
| 163 |
+
emoji: "💪",
|
| 164 |
+
color: "#dd6b20"
|
| 165 |
+
};
|
| 166 |
+
} else {
|
| 167 |
+
return {
|
| 168 |
+
message: templates.needs_improvement,
|
| 169 |
+
emoji: "📚",
|
| 170 |
+
color: "#e53e3e"
|
| 171 |
+
};
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
getRankTitle(level) {
|
| 176 |
+
const ranks = {
|
| 177 |
+
1: "تازه کار",
|
| 178 |
+
5: "یادگیرنده",
|
| 179 |
+
10: "متخصص",
|
| 180 |
+
15: "استاد",
|
| 181 |
+
20: "ارشد",
|
| 182 |
+
25: "لجن",
|
| 183 |
+
30: "اسطوره"
|
| 184 |
+
};
|
| 185 |
+
|
| 186 |
+
for (const [threshold, title] of Object.entries(ranks).reverse()) {
|
| 187 |
+
if (level >= parseInt(threshold)) {
|
| 188 |
+
return title;
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
return "تازه کار";
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
calculateStreakBonus(currentStreak, previousStreak) {
|
| 196 |
+
if (currentStreak <= previousStreak) return 0;
|
| 197 |
+
|
| 198 |
+
const streakIncrease = currentStreak - previousStreak;
|
| 199 |
+
return streakIncrease * this.streakBonus;
|
| 200 |
+
}
|
| 201 |
+
}
|