Achievements System

Rewarding players for completing specific goals through progress tracking, unlocking logic, rarity calculation, and achievement display for gamification and player engagement.

16 stars

Best use case

Achievements System is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Rewarding players for completing specific goals through progress tracking, unlocking logic, rarity calculation, and achievement display for gamification and player engagement.

Teams using Achievements System should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/achievements-system/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/achievements-system/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/achievements-system/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How Achievements System Compares

Feature / AgentAchievements SystemStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Rewarding players for completing specific goals through progress tracking, unlocking logic, rarity calculation, and achievement display for gamification and player engagement.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Achievements System

> **Current Level:** Intermediate  
> **Domain:** Gaming / Backend

---

## Overview

Achievements reward players for completing specific goals. This guide covers progress tracking, unlocking logic, and rarity calculation for building engaging achievement systems that motivate players and increase retention.

## Achievement Types

### Progress-based
- Track incremental progress
- Example: "Kill 100 enemies"

### Milestone
- One-time achievements
- Example: "Complete first level"

### Hidden
- Secret achievements
- Revealed upon unlock

### Rare
- Difficult achievements
- Limited unlock rate

## Database Schema

```sql
-- achievements table
CREATE TABLE achievements (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  key VARCHAR(100) UNIQUE NOT NULL,
  
  name VARCHAR(255) NOT NULL,
  description TEXT,
  icon_url VARCHAR(500),
  
  type VARCHAR(50) NOT NULL,
  category VARCHAR(100),
  
  hidden BOOLEAN DEFAULT FALSE,
  
  points INTEGER DEFAULT 0,
  
  requirement_type VARCHAR(50),
  requirement_value INTEGER,
  
  created_at TIMESTAMP DEFAULT NOW(),
  
  INDEX idx_key (key),
  INDEX idx_category (category)
);

-- player_achievements table
CREATE TABLE player_achievements (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  player_id UUID REFERENCES players(id) ON DELETE CASCADE,
  achievement_id UUID REFERENCES achievements(id) ON DELETE CASCADE,
  
  progress INTEGER DEFAULT 0,
  unlocked BOOLEAN DEFAULT FALSE,
  unlocked_at TIMESTAMP,
  
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  
  UNIQUE(player_id, achievement_id),
  INDEX idx_player (player_id),
  INDEX idx_unlocked (unlocked, unlocked_at)
);

-- achievement_stats table
CREATE TABLE achievement_stats (
  achievement_id UUID PRIMARY KEY REFERENCES achievements(id),
  
  total_unlocks INTEGER DEFAULT 0,
  unlock_rate DECIMAL(5,2) DEFAULT 0,
  
  updated_at TIMESTAMP DEFAULT NOW()
);
```

## Achievement Tracking

```typescript
// services/achievement.service.ts
export class AchievementService {
  async trackProgress(
    playerId: string,
    achievementKey: string,
    increment: number = 1
  ): Promise<AchievementProgress> {
    const achievement = await db.achievement.findUnique({
      where: { key: achievementKey }
    });

    if (!achievement) {
      throw new Error('Achievement not found');
    }

    // Get or create player achievement
    let playerAchievement = await db.playerAchievement.findUnique({
      where: {
        playerId_achievementId: {
          playerId,
          achievementId: achievement.id
        }
      }
    });

    if (!playerAchievement) {
      playerAchievement = await db.playerAchievement.create({
        data: {
          playerId,
          achievementId: achievement.id,
          progress: 0
        }
      });
    }

    // Update progress
    const newProgress = playerAchievement.progress + increment;
    const isComplete = newProgress >= (achievement.requirementValue || 1);

    playerAchievement = await db.playerAchievement.update({
      where: { id: playerAchievement.id },
      data: {
        progress: newProgress,
        unlocked: isComplete,
        unlockedAt: isComplete ? new Date() : null
      }
    });

    // If unlocked, update stats and notify
    if (isComplete && !playerAchievement.unlocked) {
      await this.onAchievementUnlocked(playerId, achievement.id);
    }

    return {
      achievementId: achievement.id,
      achievementKey: achievement.key,
      progress: newProgress,
      required: achievement.requirementValue || 1,
      unlocked: isComplete,
      percentage: (newProgress / (achievement.requirementValue || 1)) * 100
    };
  }

  async trackEvent(playerId: string, eventType: string, data?: any): Promise<void> {
    // Get achievements triggered by this event
    const achievements = await db.achievement.findMany({
      where: { requirementType: eventType }
    });

    for (const achievement of achievements) {
      await this.trackProgress(playerId, achievement.key, 1);
    }
  }

  private async onAchievementUnlocked(
    playerId: string,
    achievementId: string
  ): Promise<void> {
    // Update stats
    await db.achievementStats.upsert({
      where: { achievementId },
      create: {
        achievementId,
        totalUnlocks: 1
      },
      update: {
        totalUnlocks: { increment: 1 }
      }
    });

    // Calculate unlock rate
    await this.updateUnlockRate(achievementId);

    // Send notification
    await this.sendUnlockNotification(playerId, achievementId);

    // Award points
    await this.awardPoints(playerId, achievementId);
  }

  private async updateUnlockRate(achievementId: string): Promise<void> {
    const totalPlayers = await db.player.count();
    const stats = await db.achievementStats.findUnique({
      where: { achievementId }
    });

    if (stats && totalPlayers > 0) {
      const unlockRate = (stats.totalUnlocks / totalPlayers) * 100;

      await db.achievementStats.update({
        where: { achievementId },
        data: { unlockRate }
      });
    }
  }

  private async sendUnlockNotification(
    playerId: string,
    achievementId: string
  ): Promise<void> {
    const achievement = await db.achievement.findUnique({
      where: { id: achievementId }
    });

    if (achievement) {
      // Emit WebSocket event
      io.to(`player:${playerId}`).emit('achievement-unlocked', {
        id: achievement.id,
        name: achievement.name,
        description: achievement.description,
        iconUrl: achievement.iconUrl,
        points: achievement.points
      });
    }
  }

  private async awardPoints(playerId: string, achievementId: string): Promise<void> {
    const achievement = await db.achievement.findUnique({
      where: { id: achievementId }
    });

    if (achievement && achievement.points > 0) {
      await db.player.update({
        where: { id: playerId },
        data: {
          achievementPoints: { increment: achievement.points }
        }
      });
    }
  }
}

interface AchievementProgress {
  achievementId: string;
  achievementKey: string;
  progress: number;
  required: number;
  unlocked: boolean;
  percentage: number;
}
```

## Progress Calculation

```typescript
// Different progress calculation strategies
export class ProgressCalculator {
  calculateLinear(current: number, required: number): number {
    return Math.min((current / required) * 100, 100);
  }

  calculateTiered(current: number, tiers: number[]): number {
    let completedTiers = 0;
    
    for (const tier of tiers) {
      if (current >= tier) {
        completedTiers++;
      }
    }

    return (completedTiers / tiers.length) * 100;
  }

  calculateCumulative(values: number[], required: number): number {
    const total = values.reduce((sum, val) => sum + val, 0);
    return Math.min((total / required) * 100, 100);
  }
}

// Example: Multi-step achievement
const multiStepAchievement = {
  key: 'master_warrior',
  name: 'Master Warrior',
  steps: [
    { key: 'kills_100', name: 'Kill 100 enemies', required: 100 },
    { key: 'wins_50', name: 'Win 50 battles', required: 50 },
    { key: 'level_50', name: 'Reach level 50', required: 50 }
  ]
};

async function trackMultiStepProgress(playerId: string): Promise<number> {
  const steps = multiStepAchievement.steps;
  let completedSteps = 0;

  for (const step of steps) {
    const progress = await getStepProgress(playerId, step.key);
    if (progress >= step.required) {
      completedSteps++;
    }
  }

  return (completedSteps / steps.length) * 100;
}
```

## Unlocking Logic

```typescript
// services/achievement-unlock.service.ts
export class AchievementUnlockService {
  async checkUnlockConditions(
    playerId: string,
    achievementKey: string
  ): Promise<boolean> {
    const achievement = await db.achievement.findUnique({
      where: { key: achievementKey }
    });

    if (!achievement) return false;

    switch (achievement.requirementType) {
      case 'score':
        return this.checkScoreRequirement(playerId, achievement);
      
      case 'level':
        return this.checkLevelRequirement(playerId, achievement);
      
      case 'kills':
        return this.checkKillsRequirement(playerId, achievement);
      
      case 'time_played':
        return this.checkTimePlayedRequirement(playerId, achievement);
      
      case 'consecutive_wins':
        return this.checkConsecutiveWinsRequirement(playerId, achievement);
      
      default:
        return false;
    }
  }

  private async checkScoreRequirement(
    playerId: string,
    achievement: Achievement
  ): Promise<boolean> {
    const player = await db.player.findUnique({ where: { id: playerId } });
    return (player?.highScore || 0) >= (achievement.requirementValue || 0);
  }

  private async checkLevelRequirement(
    playerId: string,
    achievement: Achievement
  ): Promise<boolean> {
    const player = await db.player.findUnique({ where: { id: playerId } });
    return (player?.level || 0) >= (achievement.requirementValue || 0);
  }

  private async checkKillsRequirement(
    playerId: string,
    achievement: Achievement
  ): Promise<boolean> {
    const stats = await db.playerStats.findUnique({ where: { playerId } });
    return (stats?.totalKills || 0) >= (achievement.requirementValue || 0);
  }

  private async checkTimePlayedRequirement(
    playerId: string,
    achievement: Achievement
  ): Promise<boolean> {
    const stats = await db.playerStats.findUnique({ where: { playerId } });
    return (stats?.totalPlayTime || 0) >= (achievement.requirementValue || 0);
  }

  private async checkConsecutiveWinsRequirement(
    playerId: string,
    achievement: Achievement
  ): Promise<boolean> {
    const stats = await db.playerStats.findUnique({ where: { playerId } });
    return (stats?.currentWinStreak || 0) >= (achievement.requirementValue || 0);
  }
}
```

## Rarity Calculation

```typescript
// services/achievement-rarity.service.ts
export class AchievementRarityService {
  async calculateRarity(achievementId: string): Promise<string> {
    const stats = await db.achievementStats.findUnique({
      where: { achievementId }
    });

    if (!stats) return 'common';

    const unlockRate = stats.unlockRate;

    if (unlockRate >= 50) return 'common';
    if (unlockRate >= 20) return 'uncommon';
    if (unlockRate >= 5) return 'rare';
    if (unlockRate >= 1) return 'epic';
    return 'legendary';
  }

  async getRarityColor(rarity: string): Promise<string> {
    const colors: Record<string, string> = {
      common: '#808080',
      uncommon: '#00ff00',
      rare: '#0070dd',
      epic: '#a335ee',
      legendary: '#ff8000'
    };

    return colors[rarity] || colors.common;
  }

  async getPlayerRarityStats(playerId: string): Promise<RarityStats> {
    const achievements = await db.playerAchievement.findMany({
      where: { playerId, unlocked: true },
      include: { achievement: { include: { stats: true } } }
    });

    const rarityCount: Record<string, number> = {
      common: 0,
      uncommon: 0,
      rare: 0,
      epic: 0,
      legendary: 0
    };

    for (const pa of achievements) {
      const rarity = await this.calculateRarity(pa.achievement.id);
      rarityCount[rarity]++;
    }

    return rarityCount;
  }
}

type RarityStats = Record<string, number>;
```

## Point System

```typescript
// Achievement points based on rarity
const pointsByRarity: Record<string, number> = {
  common: 10,
  uncommon: 25,
  rare: 50,
  epic: 100,
  legendary: 250
};

async function assignPoints(achievementId: string): Promise<void> {
  const rarity = await rarityService.calculateRarity(achievementId);
  const points = pointsByRarity[rarity];

  await db.achievement.update({
    where: { id: achievementId },
    data: { points }
  });
}
```

## Social Sharing

```typescript
// services/achievement-sharing.service.ts
export class AchievementSharingService {
  async generateShareImage(
    playerId: string,
    achievementId: string
  ): Promise<string> {
    // Generate image with achievement details
    // Return URL to generated image
    return `https://cdn.example.com/achievements/${achievementId}/share.png`;
  }

  async shareToSocial(
    playerId: string,
    achievementId: string,
    platform: 'twitter' | 'facebook'
  ): Promise<string> {
    const achievement = await db.achievement.findUnique({
      where: { id: achievementId }
    });

    const player = await db.player.findUnique({
      where: { id: playerId }
    });

    if (!achievement || !player) {
      throw new Error('Not found');
    }

    const shareUrl = `https://game.example.com/achievements/${achievementId}`;
    const text = `I just unlocked "${achievement.name}" in Game! ${shareUrl}`;

    if (platform === 'twitter') {
      return `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}`;
    } else {
      return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`;
    }
  }
}
```

## Achievement Display

```typescript
// components/AchievementCard.tsx
export function AchievementCard({ achievement, progress }: AchievementCardProps) {
  const isUnlocked = progress?.unlocked || false;
  const percentage = progress?.percentage || 0;

  return (
    <div className={`achievement-card ${isUnlocked ? 'unlocked' : 'locked'}`}>
      <div className="achievement-icon">
        <img
          src={achievement.iconUrl}
          alt={achievement.name}
          style={{ filter: isUnlocked ? 'none' : 'grayscale(100%)' }}
        />
      </div>

      <div className="achievement-info">
        <h3>{achievement.name}</h3>
        <p>{achievement.hidden && !isUnlocked ? '???' : achievement.description}</p>

        {!isUnlocked && achievement.requirementValue && (
          <div className="progress-bar">
            <div
              className="progress-fill"
              style={{ width: `${percentage}%` }}
            />
            <span>{progress?.progress || 0} / {achievement.requirementValue}</span>
          </div>
        )}

        {isUnlocked && progress?.unlockedAt && (
          <p className="unlock-date">
            Unlocked: {new Date(progress.unlockedAt).toLocaleDateString()}
          </p>
        )}

        <div className="achievement-meta">
          <span className="points">{achievement.points} pts</span>
          <span className={`rarity ${achievement.rarity}`}>
            {achievement.rarity}
          </span>
        </div>
      </div>
    </div>
  );
}

interface AchievementCardProps {
  achievement: Achievement;
  progress?: AchievementProgress;
}
```

---

## Quick Start

### Achievement System

```typescript
interface Achievement {
  id: string
  name: string
  description: string
  type: 'progress' | 'milestone' | 'hidden'
  condition: AchievementCondition
  rarity: 'common' | 'rare' | 'epic' | 'legendary'
  points: number
}

interface AchievementCondition {
  type: 'kill_count' | 'level_complete' | 'time_played'
  target: number
}

async function checkAchievements(playerId: string, action: PlayerAction) {
  const achievements = await getUnlockedAchievements(playerId)
  const allAchievements = await getAllAchievements()
  
  for (const achievement of allAchievements) {
    if (achievements.includes(achievement.id)) continue
    
    if (checkCondition(achievement.condition, action)) {
      await unlockAchievement(playerId, achievement.id)
      await notifyPlayer(playerId, achievement)
    }
  }
}
```

---

## Production Checklist

- [ ] **Achievement Design**: Design achievement system
- [ ] **Progress Tracking**: Track all relevant actions
- [ ] **Unlock Logic**: Implement unlock conditions
- [ ] **Notifications**: Celebrate unlocks
- [ ] **Rarity System**: Calculate and display rarity
- [ ] **Points System**: Reward with points/badges
- [ ] **Social Features**: Enable sharing
- [ ] **Categories**: Organize achievements
- [ ] **Analytics**: Track unlock rates
- [ ] **Balance**: Mix easy and hard achievements
- [ ] **Testing**: Test unlock conditions
- [ ] **Documentation**: Document achievement system

---

## Anti-patterns

### ❌ Don't: Too Easy or Too Hard

```markdown
# ❌ Bad - Unbalanced
Achievement 1: "Play 1 game" (too easy)
Achievement 2: "Play 1,000,000 games" (too hard)
```

```markdown
# ✅ Good - Balanced progression
Achievement 1: "Play 10 games"
Achievement 2: "Play 100 games"
Achievement 3: "Play 1,000 games"
```

### ❌ Don't: No Progress Indication

```typescript
// ❌ Bad - No progress shown
if (kills >= 100) {
  unlockAchievement('kill_100')
}
// Player doesn't know progress!
```

```typescript
// ✅ Good - Show progress
const progress = kills / 100
showProgress('kill_100', progress)  // "Kill 100 enemies: 45/100"
```

---

## Integration Points

- **Leaderboards** (`38-gaming-features/leaderboards/`) - Achievement rankings
- **Game Analytics** (`38-gaming-features/game-analytics/`) - Achievement metrics
- **Matchmaking** (`38-gaming-features/matchmaking/`) - Game sessions

---

## Further Reading

- [Achievement System Design](https://www.gamedeveloper.com/design/achievement-systems)
- [Gamification Best Practices](https://www.gamify.com/gamification-blog/gamification-best-practices)

## Resources

- [Xbox Achievements](https://www.xbox.com/en-US/live/achievements)
- [PlayStation Trophies](https://www.playstation.com/en-us/support/games/ps4-trophy-information/)
- [Steam Achievements](https://partner.steamgames.com/doc/features/achievements)
- [Game Achievement Design](https://www.gamedeveloper.com/design/designing-achievements)

Related Skills

advocacy-roster-system

16
from diegosouzapw/awesome-omni-skill

Scoring and governance framework for managing reference customers and advocacy cohorts.

ui-ux-design-system

16
from diegosouzapw/awesome-omni-skill

Expert in building premium, accessible UI/UX design systems for SaaS apps. Covers design tokens, component architecture with shadcn/ui and Radix, dark mode, glassmorphism, micro-animations, responsive layouts, and accessibility. Use when: ui, ux, design system, shadcn, radix, tailwind, dark mode, animation, accessibility, components, figma to code.

tailwind-design-system

16
from diegosouzapw/awesome-omni-skill

Build scalable design systems with Tailwind CSS, design tokens, component libraries, and responsive patterns. Use when creating component libraries, implementing design systems, or standardizing UI...

setup-design-system

16
from diegosouzapw/awesome-omni-skill

Initialize the design system or create new UI components with accessibility, Tailwind/shadcn integration, and documentation. Use when setting up the initial design system, adding component categories, or creating complex UI components that need design review.

radix-ui-design-system

16
from diegosouzapw/awesome-omni-skill

Build accessible design systems with Radix UI primitives. Headless component customization, theming strategies, and compound component patterns for production-grade UI libraries.

lean-systems-design

16
from diegosouzapw/awesome-omni-skill

Apply Elon Musk-inspired system design thinking for research, engineering, and business workflows: rigorously challenge requirements, delete steps, simplify/optimize what remains, accelerate iteration, then automate. Use when designing or revising systems, processes, or products that need lean, high-velocity execution.

Design Undo/Redo Systems

16
from diegosouzapw/awesome-omni-skill

CREATE comprehensive undo/redo systems with Command Pattern. Design state management for complex applications with canvas interactions, multiple stores, and user actions. Use when building new undo/redo functionality from scratch.

design-system

16
from diegosouzapw/awesome-omni-skill

design system with Tailwind v4.0, accessibility patterns, and project-specific UI/UX rules. Use for all KKOOKK frontend development.

design-system-starter

16
from diegosouzapw/awesome-omni-skill

Create and evolve design systems with design tokens, component architecture, accessibility guidelines, and documentation templates. Ensures consistent, scalable, and accessible UI across products.

design-system-guard

16
from diegosouzapw/awesome-omni-skill

Validate UI screens against Lucid Labs design system rules. Use after implementing UI components to verify adherence to brand colors, typography, layout patterns, and service board logic.

create-design-system-rules

16
from diegosouzapw/awesome-omni-skill

Generates custom design system rules for the user's codebase. Use when user says "create design system rules", "generate rules for my project", "set up design rules", "customize design system guidelines", or wants to establish project-specific conventions for Figma-to-code workflows. Requires Figma MCP server connection.

Arcanea Design System

16
from diegosouzapw/awesome-omni-skill

Complete visual design language for Arcanea - cosmic theme tokens, component patterns, animation standards, and Academy-specific aesthetics