Skip to main content

Overview

Relaciona’s gamification system turns student profile data into interactive learning experiences. The platform includes multiple games that help students learn about their classmates while having fun.
All games use session-based scoring and AJAX for instant feedback without page reloads.

Session-Based Scoring

The gamification system tracks performance using Django session storage.

Score Tracking Pattern

From minigames/views.py:70-71, every game follows this pattern:
if 'face_guess_correct' not in request.session: 
    request.session['face_guess_correct'] = 0
if 'face_guess_total' not in request.session: 
    request.session['face_guess_total'] = 0
Each game maintains:
  • Correct score: Number of correct answers in the current session
  • Total attempts: Total number of questions answered
  • Win percentage: Calculated as correct / total

Game Prefixes

Each game uses a unique session prefix:
GamePrefix
Face Guessface_guess_
Name to Facename_to_face_
Hangmanhangman_
Student Interestsinterests_
Quiz Resultsquiz_
Complete Profilecomplete_profile_
Spotify Guessspotify_

Score Management

Incrementing Scores

On each answer submission:
request.session['face_guess_total'] += 1

if is_correct:
    request.session['face_guess_correct'] += 1

Helper Function

From minigames/views.py:25-32, a unified response builder:
def get_ajax_response(request, success, message, prefix, extra_data=None):
    data = {
        'success': success, 
        'message': message,
        'correct': request.session.get(f'{prefix}_correct', 0),
        'total': request.session.get(f'{prefix}_total', 0),
    }
    if extra_data: 
        data.update(extra_data)
    return JsonResponse(data)
This ensures consistent score reporting across all games.

AJAX Instant Feedback

Games detect AJAX requests and return JSON instead of redirecting.

Detection Pattern

From minigames/views.py:103-104:
if request.headers.get('x-requested-with') == 'XMLHttpRequest': 
    return get_ajax_response(request, is_correct, msg, 'face_guess')

Benefits

No Page Reload

Answers are submitted and scored without refreshing the page.

Instant Feedback

Students immediately see if they’re correct with custom messages.

Smooth UX

Gameplay feels fluid and responsive like modern web apps.

Anti-Repetition Logic

Games avoid showing the same student twice in a row for better engagement.

Implementation

From minigames/views.py:108-114:
# Selection avoiding repetition
last_id = request.session.get('face_guess_last_id')
available_students = students.exclude(id=last_id) if students.count() > 1 else students
target_student = random.choice(available_students)

request.session['face_guess_target_id'] = target_student.id
request.session['face_guess_last_id'] = target_student.id

How It Works

1

Store Last Student

After showing a student, store their ID in session as {game}_last_id.
2

Exclude on Next Round

When selecting next student, exclude the last ID from available options.
3

Handle Edge Cases

If only one student exists, allow repetition (no other choice).
4

Update Tracking

Store new student as both current target and last shown.
This pattern applies to Face Guess, Name to Face, Hangman, and Spotify Guess games.

Text Normalization for Answers

The hangman and face guess games accept flexible text input.

Normalization Function

From minigames/views.py:12-18:
def normalize_text(text):
    """Elimina tildes y convierte a mayúsculas."""
    if not text: return ""
    return ''.join(
        c for c in unicodedata.normalize('NFD', text)
        if unicodedata.category(c) != 'Mn'
    ).upper()
Purpose: Remove accents and convert to uppercase for comparison.

Answer Validation

From minigames/views.py:86-96, the Face Guess game uses sophisticated matching:
# Collect all possible valid answers
fields = ['username', 'first_name', 'last_name', 'full_name', 'nickname']
valid_answers = [normalize_text(getattr(student, f)).strip() 
                 for f in fields if getattr(student, f)]

# Exact match check
if answer in valid_answers:
    is_correct = True
else:
    # Partial match (e.g., "Juan" matches "Juan Alberto")
    for valid in valid_answers:
        if len(answer) > 2 and (answer in valid or valid in answer):
            is_correct = True
            break

Matching Logic

  1. Normalization: Both input and stored values are normalized (no accents, uppercase)
  2. Multiple Fields: Checks username, first_name, last_name, full_name, and nickname
  3. Exact Match: Direct equality after normalization
  4. Partial Match: Substring matching (minimum 3 characters)
  5. Bidirectional: Checks if answer is in valid name OR valid name is in answer
Examples:
  • “JUAN” matches “Juan Alberto González”
  • “Gonzalez” matches “González” (accent removed)
  • “Juanito” matches if it’s stored as nickname
  • “JU” doesn’t match (too short for partial matching)

Game-Specific Features

Hangman Game Logic

From minigames/views.py:206-237, Hangman has unique state management:
# Letter guessing
letter = request.POST.get('letter', '').upper()

if letter not in request.session['hangman_guessed_letters']:
    request.session['hangman_guessed_letters'].append(letter)
    
    if letter not in target_name:
        request.session['hangman_incorrect_count'] += 1

# Win/loss detection
displayed = "".join([
    c if c in request.session['hangman_guessed_letters'] or not c.isalpha() else "_" 
    for c in target_name
])

won = all(c in request.session['hangman_guessed_letters'] or not c.isalpha() 
          for c in target_name)
lost = request.session['hangman_incorrect_count'] >= 6

if won or lost:
    request.session['hangman_game_over'] = True
    request.session['hangman_total'] += 1
    if won: 
        request.session['hangman_correct'] += 1
Key Features:
  • Track guessed letters to prevent repeats
  • Display partial name with underscores for unknown letters
  • Allow 6 incorrect guesses before game over
  • Non-alphabetic characters (spaces) shown automatically

Quiz Results Matching

From minigames/views.py:323-326, shows a student’s VARK/Chapman results:
target = random.choice(students)
options = list(random.sample(list(students.exclude(id=target.id)), 3)) + [target]
random.shuffle(options)
Players must identify which student has the displayed learning style and emotional preference.

Complete Profile Game

From minigames/views.py:369-381, the most complex game combines multiple data points:
for s in raw_options:
    vark = UserResult.objects.filter(user=s, questionnaire=vark_q).first()
    chapman = UserResult.objects.filter(user=s, questionnaire=chapman_q).first()
    options_data.append({
        'student_id': s.id,
        'full_name': s.full_name or s.username,
        'age': calculate_age(s.date_of_birth),
        'favorite_artist': s.favorite_artist or "N/A",
        'vark_result': vark.dominant_category if vark else "Pte",
        'chapman_result': chapman.dominant_category if chapman else "Pte",
        'motivation': s.motivation or "N/A"
    })
Displays comprehensive profile data and tests knowledge of classmates’ complete profiles.

Student Filtering

Games filter available students based on profile completeness.

Common Patterns

# Face-based games: Require profile pictures
students = group.students.filter(
    profile_picture__isnull=False
).exclude(id=request.user.id).distinct()

# Spotify game: Require Spotify links
students = group.students.exclude(
    spotify_link__isnull=True
).exclude(spotify_link='').exclude(id=request.user.id).distinct()

# General games: Just exclude self
students = group.students.all().exclude(id=request.user.id).distinct()
Key Points:
  • Always exclude the current user (can’t guess yourself)
  • Filter by required fields for game functionality
  • Use .distinct() to avoid duplicate students

Minimum Student Requirements

From minigames/views.py:136, many games require minimum participants:
if students.count() < 4: 
    return render(request, 'minigames/not_enough_students.html')
Multiple-choice games need at least 4 students (target + 3 distractors).

Group Selection System

From minigames/views.py:35-48, games support both student and teacher access:
@login_required
def select_group_for_game(request, game_name):
    """ Intermediate screen to choose which class will play """
    if request.user.role == 'student':
        mi_grupo = request.user.student_groups.first()
        if not mi_grupo:
            return render(request, 'minigames/no_students.html')
        return redirect(game_name, group_id=mi_grupo.id)

    groups = ClassGroup.objects.filter(teacher=request.user)
    return render(request, 'minigames/select_group.html', {
        'groups': groups,
        'game_name': game_name
    })

Student Access

Students automatically play with their assigned class group. No selection needed.

Teacher Access

Teachers choose which class to use for the game from their managed groups.

Age Calculator Utility

From minigames/views.py:20-23, used in several games:
def calculate_age(birth_date):
    if not birth_date: return "desconocida"
    today = date.today()
    return today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))
Correctly handles birthdays that haven’t occurred yet this year.

Game Roster

Relaciona includes these gamified activities:
Type: Text inputShow a profile picture, player types the student’s name.
  • Flexible answer matching (multiple name fields, partial matches)
  • Anti-repetition logic
  • Requires profile pictures
Type: Multiple choiceShow a name, player selects the matching profile picture from 4 options.
  • Random option shuffling
  • Target + 3 distractors
  • Requires 4+ students with pictures
Type: Letter guessingClassic hangman with student names.
  • 6 incorrect guesses allowed
  • Tracks guessed letters
  • Shows partial progress
  • Text normalization for accents
Type: Multiple choiceMatch a student’s profile picture to their description (age, favorite artist, motivation).
  • Dynamically generates descriptions
  • 4 student options
  • Tests knowledge of personal interests
Type: Multiple choiceShow VARK and Chapman results, identify the student.
  • Uses learning assessment data
  • 4 student options
  • Tests knowledge of learning preferences
Type: Multiple choiceMost comprehensive game showing age, favorite artist, VARK, Chapman, and motivation.
  • Combines multiple data sources
  • Shows “Pte” (pending) for incomplete assessments
  • 4 student options
  • Tests deep knowledge of classmates
Type: Multiple choiceListen to a Spotify song preview, guess whose favorite song it is.
  • Embedded Spotify player
  • Requires 4+ students with Spotify links
  • Anti-repetition logic
  • Tests knowledge of music preferences

Technical Architecture

Session Storage

Advantages:
  • No database writes for every answer
  • Fast score updates
  • Automatic cleanup on logout
  • Per-user isolated scores
Considerations:
  • Scores reset when session expires
  • Not suitable for persistent leaderboards
  • Requires request.session.modified = True after complex updates

AJAX Pattern

// Frontend (typical)
fetch('/game/', {
    method: 'POST',
    headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'X-CSRFToken': getCookie('csrftoken')
    },
    body: formData
})
.then(response => response.json())
.then(data => {
    // Update score display
    document.getElementById('score').textContent = 
        `${data.correct} / ${data.total}`;
    // Show feedback message
    showMessage(data.message, data.success);
});

Future Enhancements

The current system is session-based. Potential enhancements could include:
  • Persistent leaderboards across sessions
  • Historical performance tracking
  • Badges and achievements
  • Time-based challenges
  • Multiplayer simultaneous gameplay
  • Performance analytics for teachers

Build docs developers (and LLMs) love