You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

12 KiB

Know Foolery - Game Mechanics Documentation

Overview

Know Foolery is a quiz game where players answer questions to earn points within a time-limited session. The game emphasizes strategic thinking through its hint system and attempt limitations.

Core Game Flow

1. Game Session Initialization

Player Input: Name → Session Creation → Question Selection → Game Start

Process:

  1. Player enters their name (required)
  2. System creates new game session
  3. System randomly selects first question
  4. 30-minute timer starts
  5. Game begins with question display

Validation:

  • Player name: 2-50 characters, alphanumeric + spaces
  • Session uniqueness: One active session per player
  • Timer initialization: Server-side timestamp

2. Question Presentation

Question Structure:

interface Question {
  id: string
  theme: string        // e.g., "Geography", "History", "Science"
  text: string         // The actual question
  answer: string       // Correct answer (case-insensitive)
  hint: string         // Optional hint text
  difficulty?: number  // Future enhancement
}

Display Format:

┌─────────────────────────────────────┐
│ Theme: Geography            ⏱️ 28:45 │
├─────────────────────────────────────┤
│                                     │
│ What is the capital of France?      │
│                                     │
│ [Your answer here...        ] 💡    │
│                                     │
│ Attempts remaining: 3/3             │
│ Current score: 4 points             │
└─────────────────────────────────────┘

3. Answer Submission Process

Player Actions:

  1. Direct Answer: Submit answer without hint
  2. Request Hint: View hint before answering
  3. Skip Question: Move to next question (future enhancement)

Answer Validation:

// Server-side validation logic
func ValidateAnswer(userAnswer, correctAnswer string) bool {
    // Normalize both answers
    user := strings.ToLower(strings.TrimSpace(userAnswer))
    correct := strings.ToLower(strings.TrimSpace(correctAnswer))
    
    // Exact match
    if user == correct {
        return true
    }
    
    // Fuzzy matching (future enhancement)
    similarity := calculateSimilarity(user, correct)
    return similarity > 0.85
}

Scoring System

Point Values

  • Correct without hint: 2 points
  • Correct with hint: 1 point
  • Incorrect answer: 0 points
  • Session timeout: 0 points for remaining questions

Scoring Logic

type AttemptResult struct {
    IsCorrect  bool
    UsedHint   bool
    AttemptNum int  // 1, 2, or 3
}

func CalculateScore(result AttemptResult) int {
    if !result.IsCorrect {
        return 0
    }
    
    if result.UsedHint {
        return 1  // Reduced score for using hint
    }
    
    return 2  // Full score for direct correct answer
}

Score Tracking

type GameSession struct {
    PlayerName      string
    TotalScore      int
    QuestionsAsked  int
    QuestionsCorrect int
    HintsUsed       int
    StartTime       time.Time
    EndTime         *time.Time
    IsActive        bool
}

Attempt System

Attempt Rules

  • Maximum Attempts: 3 per question
  • Attempt Tracking: Server-side validation
  • Attempt Consequences:
    • Attempt 1-3: Normal scoring if correct
    • After 3 failed attempts: Move to next question automatically

Attempt Flow

type QuestionAttempt struct {
    SessionID    string
    QuestionID   string
    AttemptNum   int        // 1, 2, or 3
    Answer       string
    IsCorrect    bool
    UsedHint     bool
    Timestamp    time.Time
}

func ProcessAttempt(sessionID, questionID, answer string) AttemptResult {
    // 1. Validate session is active
    // 2. Check attempts remaining
    // 3. Validate answer
    // 4. Update score if correct
    // 5. Move to next question if needed
}

Hint System

Hint Mechanics

  • Availability: One hint per question
  • Timing: Can be requested before any attempt
  • Impact: Reduces maximum score from 2 to 1 points
  • Content: Provides helpful clue without giving away answer

Hint Implementation

interface HintRequest {
  sessionId: string
  questionId: string
}

interface HintResponse {
  hint: string
  scoreImpact: string  // "Using this hint will reduce your score to 1 point"
  attemptsRemaining: number
}

Hint Examples

const hintExamples = {
  question: "What is the capital of France?",
  hint: "This city is famous for the Eiffel Tower and is located on the Seine River.",
  
  question: "Who painted the Mona Lisa?",
  hint: "This Italian Renaissance artist was also an inventor and scientist.",
  
  question: "What is the largest planet in our solar system?",
  hint: "This gas giant is named after the king of the Roman gods."
}

Time Management

Session Timer

  • Duration: 30 minutes maximum per session
  • Display: Real-time countdown timer
  • Warnings: Visual alerts at 5 minutes and 1 minute remaining
  • Timeout: Automatic session termination when time expires

Timer Implementation

type SessionTimer struct {
    SessionID   string
    StartTime   time.Time
    Duration    time.Duration  // 30 minutes
    WarningsSent map[int]bool  // Track warnings sent
}

func (t *SessionTimer) TimeRemaining() time.Duration {
    elapsed := time.Since(t.StartTime)
    remaining := t.Duration - elapsed
    if remaining < 0 {
        return 0
    }
    return remaining
}

func (t *SessionTimer) IsExpired() bool {
    return t.TimeRemaining() == 0
}

Timer Events

interface TimerEvent {
  type: 'warning' | 'timeout'
  timeRemaining: number  // seconds
  message: string
}

// Timer warnings
const timerWarnings = [
  { at: 300, message: "5 minutes remaining!" },   // 5 minutes
  { at: 60,  message: "1 minute remaining!" },    // 1 minute
  { at: 10,  message: "10 seconds left!" }        // 10 seconds
]

Question Selection

Random Selection Algorithm

func SelectRandomQuestion(sessionID string, excludeAnswered bool) (*Question, error) {
    var questions []Question
    query := db.Question.Query()
    
    if excludeAnswered {
        // Exclude questions already answered in this session
        answeredIDs := getAnsweredQuestions(sessionID)
        query = query.Where(question.IDNotIn(answeredIDs...))
    }
    
    questions, err := query.All(ctx)
    if err != nil {
        return nil, err
    }
    
    if len(questions) == 0 {
        return nil, errors.New("no more questions available")
    }
    
    // Random selection
    randomIndex := rand.Intn(len(questions))
    return &questions[randomIndex], nil
}

Question Difficulty (Future Enhancement)

type DifficultyLevel int

const (
    Easy DifficultyLevel = iota + 1
    Medium
    Hard
)

// Adaptive difficulty based on player performance
func SelectQuestionByDifficulty(sessionID string, targetDifficulty DifficultyLevel) (*Question, error) {
    // Implementation for adaptive difficulty
    // Consider player's success rate, current score, etc.
}

Leaderboard System

Leaderboard Calculation

type LeaderboardEntry struct {
    PlayerName      string
    Score           int
    QuestionsAnswered int
    SuccessRate     float64  // Percentage of correct answers
    SessionDuration time.Duration
    CompletedAt     time.Time
}

func CalculateLeaderboard() ([]LeaderboardEntry, error) {
    sessions := getCompletedSessions()
    
    entries := make([]LeaderboardEntry, 0, len(sessions))
    for _, session := range sessions {
        entry := LeaderboardEntry{
            PlayerName:      session.PlayerName,
            Score:          session.TotalScore,
            QuestionsAnswered: session.QuestionsAsked,
            SuccessRate:    float64(session.QuestionsCorrect) / float64(session.QuestionsAsked) * 100,
            SessionDuration: session.EndTime.Sub(session.StartTime),
            CompletedAt:    *session.EndTime,
        }
        entries = append(entries, entry)
    }
    
    // Sort by score (descending), then by completion time (ascending)
    sort.Slice(entries, func(i, j int) bool {
        if entries[i].Score == entries[j].Score {
            return entries[i].CompletedAt.Before(entries[j].CompletedAt)
        }
        return entries[i].Score > entries[j].Score
    })
    
    // Return top 10
    if len(entries) > 10 {
        entries = entries[:10]
    }
    
    return entries, nil
}

Leaderboard Display

🏆 LEADERBOARD

Rank | Player    | Score | Questions | Success Rate | Duration
-----|-----------|-------|-----------|--------------|----------
  1  | Alice     |   24  |    14     |     86%      |   28m
  2  | Bob       |   22  |    13     |     85%      |   25m  
  3  | Charlie   |   20  |    12     |     83%      |   30m
  4  | Diana     |   18  |    11     |     82%      |   22m
  5  | Eve       |   16  |    10     |     80%      |   27m

Game State Management

Session States

type SessionState int

const (
    SessionCreated SessionState = iota
    SessionActive
    SessionPaused    // Future enhancement
    SessionCompleted
    SessionTimedOut
    SessionAbandoned
)

type GameSession struct {
    ID              string
    PlayerName      string
    State           SessionState
    CurrentQuestion *Question
    Score           int
    // ... other fields
}

State Transitions

Created → Active → {Completed, TimedOut, Abandoned}
                ↓
              Paused → Active (future enhancement)

Anti-Cheating Measures

Server-Side Validation

type SecurityCheck struct {
    SessionID       string
    QuestionID      string
    ExpectedAnswer  string
    SubmittedAnswer string
    TimestampSubmit time.Time
    TimestampShow   time.Time
    IsValid         bool
    Reason          string
}

func ValidateSubmission(check SecurityCheck) bool {
    // 1. Check minimum time between question show and answer submit
    minTime := 2 * time.Second
    if check.TimestampSubmit.Sub(check.TimestampShow) < minTime {
        check.Reason = "Answer submitted too quickly"
        return false
    }
    
    // 2. Validate session state
    session := getSession(check.SessionID)
    if session.State != SessionActive {
        check.Reason = "Invalid session state"
        return false
    }
    
    // 3. Check if question belongs to session
    if session.CurrentQuestion.ID != check.QuestionID {
        check.Reason = "Question mismatch"
        return false
    }
    
    return true
}

Client-Side Integrity

// Prevent common cheating attempts
class GameIntegrity {
  private questionStartTime: number = 0
  
  onQuestionDisplayed() {
    this.questionStartTime = Date.now()
    
    // Disable browser dev tools (basic deterrent)
    this.disableDevTools()
    
    // Prevent copy-paste in answer input
    this.disableCopyPaste()
  }
  
  onAnswerSubmit(answer: string) {
    const timeTaken = Date.now() - this.questionStartTime
    
    // Include timing in submission for server validation
    return {
      answer,
      timeTaken,
      timestamp: Date.now()
    }
  }
  
  private disableDevTools() {
    // Basic dev tools detection (not foolproof)
    const devtools = {
      open: false,
      orientation: null
    }
    
    const threshold = 160
    setInterval(() => {
      if (window.outerHeight - window.innerHeight > threshold || 
          window.outerWidth - window.innerWidth > threshold) {
        console.warn('Developer tools detected')
        // Log security event
      }
    }, 500)
  }
}

Future Enhancements

Planned Features

  1. Question Categories: Filter questions by subject
  2. Difficulty Progression: Adaptive difficulty based on performance
  3. Multiplayer Mode: Real-time competition between players
  4. Daily Challenges: Special themed question sets
  5. Achievement System: Badges and milestones
  6. Question Contributions: Player-submitted questions

Scoring Enhancements

  1. Time Bonus: Extra points for quick correct answers
  2. Streak Bonus: Consecutive correct answers bonus
  3. Difficulty Multiplier: Higher points for harder questions
  4. Perfect Game Bonus: Bonus for 100% correct rate

This game mechanics documentation ensures consistent implementation across all platforms and provides clear guidelines for future enhancements.