TGViewer
C# Short Posts 🔞 C# Short Posts 🔞 @dimasshortposts · 309 subscribers
Post #176 188
🏆 Как я сделал систему достижений для Тралебота

Мне нравятся достижения (далее ачивки). Не подумайте, что я любитель позадрачивать все вышки в играх от Ubisoft! Но кольца в фитнес-приложениях заставляют меня периодически оторвать зад от дивана. Поэтому я решил впиндюрить систему ачивок и в тралебота, чтобы не только зад от дивана отрывать, но и слова английские учить.

Коротко расскажу, что хотелось, и как получилось:
Хотелось:
1️⃣ Чтобы проверку ачивки можно было бы вызвать одной строчкой.
2️⃣ Всю систему должно быть легко выделить в отдельный сервис и дергать по АПИ.
3️⃣ Не нужно мигрировать базу, если название ачивки поменялось.
4️⃣ Легко добавить новую ачивку в виде класса с условиями проверки.
5️⃣ Если нужно, эти классы с условием проверок можно превратить во что-то более абстрактное и хранить в базе данных.
6️⃣ Несложно сделать переводы ачивок на разные языки при случае.
7️⃣ Можно посмотреть открытые и не открытые ачивки, но хранить в базе для пользователя только открытые.

Получилось так:
Создал три верхнеуровненвые абстракции: IAchievementsService, IAchievementChecker<TTrigger>, IAchievementTrigger,

Вот как они связаны:
К примеру мы хотим наградить человека за то, что он получил 10 золотых медалей медалей.
1️⃣ Вытаскиваем из базы сколько медалей он получил и отправляем триггер с этой информацией в achievementService:

var wordMasteringLevelTrigger = new WordMasteringLevelTrigger
{
GoldMedalWordsCount = goldMedalsCount,
};

await _achievementsService.AssignAchievements(wordMasteringLevelTrigger,request.UserId,ct);

2️⃣ Сам achievementService пробегается по куче разных чекеров и смотрит, подошло ли условие одного из них.

private readonly IEnumerable<IAchievementChecker<IAchievementTrigger>> _achievementCheckers;

private List<Achievement> AssignAchievements <T> (T trigger, User user)
{
var unlockedAchievements = new List<Achievement>();

foreach (var achievementChecker in _achievementCheckers)
{
if( achievementChecker is {} checker
&& checker.CheckAchievement(trigger))
{
var achievement = checker.MapToAchievement();
unlockedAchievements.Add(achievement);
}
}

\\ тут еще код, который достает из базы
\\ уже открытые ачивки и клеит их с новыми

return unlockedAchievements;
}

3️⃣ Код IAchievementChecker, содержит информацию о названии ачивки, иконку и условие проверки. Выглядит вот так:

public class MedalistChecker: IAchievementChecker<WordMasteringLevelTrigger>
{
public string Icon => "🥉";
public string Name => "Медалист";
public string Description => "10 слов с золотой медалью";
public Guid AchievementTypeId => Guid.Parse("574316E8-E3BA-4BD1-92DB-61409C85E0ED");
public bool CheckAchievement(object trigger)
{
if (trigger is not WordMasteringLevelTrigger kingOfScoreTrigger)
{
return false;
}

var medalWordsCount = kingOfScoreTrigger.GoldMedalWordsCount;
return medalWordsCount>=10;
}
}

📝 Есть пара вещей, которая меня смутила, но я так и не понял, плохо это или хорошо:
1️⃣ Внутри чекера хранится и описание ачивки и код проверки. Что-то в этом есть крамольное, но пока не знаю, что.
2️⃣ Бывает за один юзкейс нужно вызвать этот AssignAchievements() аж три раза. Не каждый вызов идет в базу, но код хэндлеров наполняется вызовами AssignAchievements, не имеющими прямого отношения к логике. Вынести их в бихейвиор или что-то такое, тоже проблематично, потому в юзкейсах есть ифчики, внутри которых я вызываю эти самые методы проверки.

Еще был вариант с джобой, которая периодически опрашивает базу, на предмет удовлетворения условиям ачивки. Но мне такой вариант не нравился тем, что ачивка получается не сразу после действия, а с задержкой. Поэтому оставил как есть.

🅰️ Итого: Вообще звучит, как неплохая задачка для собеса. Хз, прошел бы я с таким решением куда-то или нет, но как минимум сам себя в свой стартап я бы точно устроил 😈
  • 🏆 3
  • 👍 2
  • 🔥 2
More from @dimasshortposts
  1. Sep 30, 2026🛤 Путь до Tier1: от чего он зависит В прошлый раз мы гоняли hello world в цикле и увидели…
  2. Sep 26, 2026🧵 Тредик для вопросов по докладу про MAF на дотнексте В докладе многие подробности опусти…
  3. Sep 23, 2026Даже самым хардкорным ребятам надо отдыхать, так что отдыхаем, мои чюваки 🕺 🧑‍💻dp🥁 #he…
  4. Sep 16, 2026🎯 Instrumented Tier0: профилирование кода В прошлый раз мы разобрали два уровня компиляци…
  5. Sep 15, 2026🔜 Готовлюсь к DOTNEXT 2026 В прошлом году за две недели до выступления я зачитывал свой д…
  6. Sep 9, 2026C# Short Posts 🔞 pinned «🐸 О чём этот канал? Кажется, я уже достаточно давно веду этот к…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →