diff --git a/README_DIFFICULTY.md b/README_DIFFICULTY.md new file mode 100644 index 0000000..fb70b1c --- /dev/null +++ b/README_DIFFICULTY.md @@ -0,0 +1,19 @@ +# Difficulty Levels + +Four difficulty levels with increasing challenges and rewards. + +## Levels + +| Level | Multiplier | Time | Unlock | +|-------|------------|------|--------| +| Easy 🌱 | 1.0x | 60s | Default | +| Medium ⚡ | 1.5x | 45s | 1000 pts | +| Hard 🔥 | 2.0x | 30s | 5000 pts | +| Expert 💎 | 3.0x | 20s | 15000 pts | + +## Features + +- Progressive difficulty unlock system +- Point multipliers for harder difficulties +- Visual indicators for locked levels +- Time-based challenges diff --git a/frontend/src/app/difficulty/layout.tsx b/frontend/src/app/difficulty/layout.tsx new file mode 100644 index 0000000..5031780 --- /dev/null +++ b/frontend/src/app/difficulty/layout.tsx @@ -0,0 +1,4 @@ +import { ReactNode } from 'react'; +export default function DifficultyLayout({ children }: { children: ReactNode }) { + return <>{children}; +} diff --git a/frontend/src/app/difficulty/page.tsx b/frontend/src/app/difficulty/page.tsx new file mode 100644 index 0000000..2a7ed14 --- /dev/null +++ b/frontend/src/app/difficulty/page.tsx @@ -0,0 +1,24 @@ +'use client'; + +import React from 'react'; +import { DifficultySelector } from '@/components/difficulty'; +import { useDifficulty } from '@/hooks/useDifficulty'; + +export default function DifficultyPage() { + const { selectedDifficulty, unlockedDifficulties, selectDifficulty } = useDifficulty(); + + return ( +
+
+

+ Select Difficulty +

+ +
+
+ ); +} diff --git a/frontend/src/components/difficulty/DifficultyCard.tsx b/frontend/src/components/difficulty/DifficultyCard.tsx new file mode 100644 index 0000000..cece33e --- /dev/null +++ b/frontend/src/components/difficulty/DifficultyCard.tsx @@ -0,0 +1,37 @@ +import { DifficultyLevel, DifficultyConfig, DIFFICULTY_LEVELS } from '@/constants/difficulty'; + +interface DifficultyCardProps { + difficulty: DifficultyConfig; + isSelected: boolean; + isUnlocked: boolean; + onSelect: () => void; +} + +export const DifficultyCard: React.FC = ({ + difficulty, + isSelected, + isUnlocked, + onSelect, +}) => { + return ( + + ); +}; diff --git a/frontend/src/components/difficulty/DifficultySelector.tsx b/frontend/src/components/difficulty/DifficultySelector.tsx new file mode 100644 index 0000000..8383b8b --- /dev/null +++ b/frontend/src/components/difficulty/DifficultySelector.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { DifficultyCard } from './DifficultyCard'; +import { DifficultyLevel, DIFFICULTY_LEVELS } from '@/constants/difficulty'; + +interface DifficultySelectorProps { + selected: DifficultyLevel; + onSelect: (level: DifficultyLevel) => void; + unlockedLevels: DifficultyLevel[]; +} + +export const DifficultySelector: React.FC = ({ + selected, + onSelect, + unlockedLevels, +}) => { + return ( +
+ {DIFFICULTY_LEVELS.map((difficulty) => ( + onSelect(difficulty.level)} + /> + ))} +
+ ); +}; diff --git a/frontend/src/components/difficulty/index.ts b/frontend/src/components/difficulty/index.ts new file mode 100644 index 0000000..a4e98ea --- /dev/null +++ b/frontend/src/components/difficulty/index.ts @@ -0,0 +1,2 @@ +export { DifficultyCard } from './DifficultyCard'; +export { DifficultySelector } from './DifficultySelector'; diff --git a/frontend/src/components/leaderboard/LeaderboardList.tsx b/frontend/src/components/leaderboard/LeaderboardList.tsx new file mode 100644 index 0000000..80879fd --- /dev/null +++ b/frontend/src/components/leaderboard/LeaderboardList.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { LeaderboardEntry } from '@/types/leaderboard'; + +interface LeaderboardListProps { + entries: LeaderboardEntry[]; +} + +export const LeaderboardList: React.FC = ({ entries }) => { + return ( +
+ {entries.map((entry) => ( +
+
+ + {entry.rank === 1 ? '🥇' : entry.rank === 2 ? '🥈' : entry.rank === 3 ? '🥉' : `#${entry.rank}`} + +
+

{entry.username}

+

{entry.address}

+
+
+
+

{entry.score}

+

{entry.gamesPlayed} games

+
+
+ ))} +
+ ); +}; diff --git a/frontend/src/components/leaderboard/index.ts b/frontend/src/components/leaderboard/index.ts new file mode 100644 index 0000000..3a67b0c --- /dev/null +++ b/frontend/src/components/leaderboard/index.ts @@ -0,0 +1 @@ +export { LeaderboardList } from './LeaderboardList'; diff --git a/frontend/src/constants/difficulty.ts b/frontend/src/constants/difficulty.ts new file mode 100644 index 0000000..09a8996 --- /dev/null +++ b/frontend/src/constants/difficulty.ts @@ -0,0 +1,62 @@ +export type DifficultyLevel = 'easy' | 'medium' | 'hard' | 'expert'; + +export interface DifficultyConfig { + level: DifficultyLevel; + name: string; + description: string; + icon: string; + color: string; + pointMultiplier: number; + timeLimit: number; + unlockRequirement?: number; +} + +export const DIFFICULTY_LEVELS: DifficultyConfig[] = [ + { + level: 'easy', + name: 'Easy', + description: 'Perfect for beginners', + icon: '🌱', + color: '#10B981', + pointMultiplier: 1.0, + timeLimit: 60, + }, + { + level: 'medium', + name: 'Medium', + description: 'A balanced challenge', + icon: '⚡', + color: '#F59E0B', + pointMultiplier: 1.5, + timeLimit: 45, + unlockRequirement: 1000, + }, + { + level: 'hard', + name: 'Hard', + description: 'Test your knowledge', + icon: '🔥', + color: '#EF4444', + pointMultiplier: 2.0, + timeLimit: 30, + unlockRequirement: 5000, + }, + { + level: 'expert', + name: 'Expert', + description: 'Only for masters', + icon: '💎', + color: '#8B5CF6', + pointMultiplier: 3.0, + timeLimit: 20, + unlockRequirement: 15000, + }, +]; + +export const getDifficultyByLevel = (level: DifficultyLevel) => + DIFFICULTY_LEVELS.find(d => d.level === level); + +export const calculatePoints = (basePoints: number, difficulty: DifficultyLevel) => { + const config = getDifficultyByLevel(difficulty); + return Math.round(basePoints * (config?.pointMultiplier || 1)); +}; diff --git a/frontend/src/hooks/useDifficulty.ts b/frontend/src/hooks/useDifficulty.ts new file mode 100644 index 0000000..d034c7e --- /dev/null +++ b/frontend/src/hooks/useDifficulty.ts @@ -0,0 +1,29 @@ +import { useState, useCallback, useEffect } from 'react'; +import { DifficultyLevel } from '@/constants/difficulty'; + +export const useDifficulty = (initialLevel: DifficultyLevel = 'easy') => { + const [selectedDifficulty, setSelectedDifficulty] = useState(initialLevel); + const [unlockedDifficulties, setUnlockedDifficulties] = useState(['easy']); + + const unlockDifficulty = useCallback((level: DifficultyLevel) => { + setUnlockedDifficulties((prev) => { + if (!prev.includes(level)) { + return [...prev, level]; + } + return prev; + }); + }, []); + + const selectDifficulty = useCallback((level: DifficultyLevel) => { + if (unlockedDifficulties.includes(level)) { + setSelectedDifficulty(level); + } + }, [unlockedDifficulties]); + + return { + selectedDifficulty, + unlockedDifficulties, + selectDifficulty, + unlockDifficulty, + }; +}; diff --git a/frontend/src/hooks/useGameStats.ts b/frontend/src/hooks/useGameStats.ts new file mode 100644 index 0000000..d270aa5 --- /dev/null +++ b/frontend/src/hooks/useGameStats.ts @@ -0,0 +1,26 @@ +import { useState, useEffect } from 'react'; +import { GameStats } from '@/types/stats'; + +export const useGameStats = () => { + const [stats, setStats] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchStats = async () => { + await new Promise((r) => setTimeout(r, 300)); + setStats({ + totalGamesPlayed: 50, + totalQuestionsAnswered: 500, + correctAnswers: 420, + totalPointsEarned: 25000, + highestScore: 9500, + currentStreak: 7, + longestStreak: 15, + }); + setIsLoading(false); + }; + fetchStats(); + }, []); + + return { stats, isLoading }; +}; diff --git a/frontend/src/hooks/useLeaderboard.ts b/frontend/src/hooks/useLeaderboard.ts new file mode 100644 index 0000000..eb491ee --- /dev/null +++ b/frontend/src/hooks/useLeaderboard.ts @@ -0,0 +1,25 @@ +import { useState, useEffect } from 'react'; +import { LeaderboardEntry } from '@/types/leaderboard'; + +export const useLeaderboard = (limit = 10) => { + const [entries, setEntries] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchLeaderboard = async () => { + await new Promise((r) => setTimeout(r, 300)); + const mockEntries: LeaderboardEntry[] = Array.from({ length: limit }, (_, i) => ({ + rank: i + 1, + address: `0x${(i * 1111).toString(16)}...${(i * 9999).toString(16).slice(-4)}`, + username: `Player${i + 1}`, + score: 10000 - i * 500, + gamesPlayed: 50 - i * 2, + })); + setEntries(mockEntries); + setIsLoading(false); + }; + fetchLeaderboard(); + }, [limit]); + + return { entries, isLoading }; +}; diff --git a/frontend/src/types/leaderboard.ts b/frontend/src/types/leaderboard.ts new file mode 100644 index 0000000..b245219 --- /dev/null +++ b/frontend/src/types/leaderboard.ts @@ -0,0 +1,7 @@ +export interface LeaderboardEntry { + rank: number; + address: string; + username: string; + score: number; + gamesPlayed: number; +} diff --git a/frontend/src/types/stats.ts b/frontend/src/types/stats.ts new file mode 100644 index 0000000..7eac6a4 --- /dev/null +++ b/frontend/src/types/stats.ts @@ -0,0 +1,9 @@ +export interface GameStats { + totalGamesPlayed: number; + totalQuestionsAnswered: number; + correctAnswers: number; + totalPointsEarned: number; + highestScore: number; + currentStreak: number; + longestStreak: number; +} diff --git a/frontend/src/utils/statsUtils.ts b/frontend/src/utils/statsUtils.ts new file mode 100644 index 0000000..af8ab8d --- /dev/null +++ b/frontend/src/utils/statsUtils.ts @@ -0,0 +1,11 @@ +export const calculateAccuracy = (correct: number, total: number): number => { + if (total === 0) return 0; + return Math.round((correct / total) * 100); +}; + +export const formatPoints = (points: number): string => { + if (points >= 1000) { + return `${(points / 1000).toFixed(1)}k`; + } + return points.toString(); +};