SuperAPI AI Directory

classroom-multiplayer-host

Office & productivity

View repository

Updated 10-28 21:26

About this skill

Classroom Multiplayer Host (classroom-multiplayer-host)

Agen spesialis untuk menyajikan, mendistribusikan, dan memandu sesi permainan edukatif multiplayer (board game quiz, trivia cerdas cermat, lomba cepat tepat, dan petualangan kelompok) di dalam ruang kelas fisik maupun hybrid learning secara serentak (real-time).

Sistem ini memecahkan kendala klasik infrastruktur sekolah/kampus:

  1. Tanpa perlu beli domain publik atau sewa VPS cloud.
  2. Tanpa konfigurasi rumit port-forwarding pada router sekolah.
  3. Mendukung mode 100% Offline LAN/Hotspot (hemat kuota, latensi < 10ms).
  4. Mendukung mode Zero-Config Instant HTTPS Tunneling jika ada siswa daring atau router sekolah mengaktifkan AP Client Isolation.
  5. Pencatatan Nilai Otomatis (Real-Time Assessment Ledger): setiap interaksi, jawaban, dan poin tersimpan otomatis menjadi buku nilai siap ekspor ke Excel/JSON untuk diintegrasikan ke nilai rapor/OBE.

🌐 1. Arsitektur Jaringan Ganda (Dual-Network Modes)

               +-------------------------------------------------+
               |             LAPTOP GURU / DOSEN                 |
               |   - Host Server (Python / Node.js WebSocket)    |
               |   - Layar Proyektor / IFP (/host /projector)    |
               |   - Real-time Assessment & Score Ledger Engine  |
               +-------------------------------------------------+
                                      |
                 +--------------------+--------------------+
                 |                                         |
        [ MODE 1: LAN / OFFLINE ]              [ MODE 2: ZERO-CONFIG TUNNEL ]
        - Wi-Fi Kelas / Hotspot HP             - Cloudflare Quick Tunnel / Untun
        - IP: http://192.168.1.X:8000          - URL: https://xxx.trycloudflare.com
        - 100% Bebas Kuota Internet            - Aman HTTPS, Tanpa Port Forwarding
                 |                                         |
                 +--------------------+--------------------+
                                      |
                        +---------------------------+
                        |  SMARTPHONE / LAPTOP SISWA|
                        |   Scan QR Code / Akses Web|
                        |   Mobile-View First Client|
                        |   Input Nama & NIS / NIM  |
                        |   Tombol Jawaban & Buzzer |
                        +---------------------------+

Mode 1: Pure LAN / Hotspot Mode (100% Offline & Cepat)

  • Kapan Digunakan: Di ruang kelas dengan Wi-Fi lokal, atau guru menyalakan Personal Hotspot di smartphone.
  • Keunggulan:
    • Sama sekali tidak menghabiskan kuota internet (zero internet bandwidth).
    • Latensi instan (<10 milidetik), sangat adil untuk kuis adu cepat (buzzer race).
    • Siswa cukup menghubungkan Wi-Fi ke hotspot yang sama dan memindai QR Code di layar proyektor.
  • Deteksi IP Otomatis: Server otomatis mendeteksi kartu jaringan aktif (192.168.x.x atau 172.x.x.x) dan mencetak QR Code ASCII langsung di terminal dan layar proyektor.

Mode 2: Zero-Config Instant HTTPS Tunneling (Bebas Hambatan)

  • Kapan Digunakan:
    • Wi-Fi sekolah mengaktifkan fitur Client AP Isolation (fitur keamanan router yang mencegah antar smartphone saling berkirim data via IP lokal).
    • Kelas hybrid di mana ada siswa yang belajar dari rumah (remote learning).
  • Solusi Tanpa Setup Akun / Domain:
    • Menggunakan Cloudflare Quick Tunnels (npx untun tunnel --port 8000 atau cloudflared tunnel --url http://localhost:8000) atau LocalTunnel (npx localtunnel --port 8000).
    • Dalam 2 detik menghasilkan tautan aman instan: https://learn-xyz.trycloudflare.com.
    • Tanpa registrasi akun, tanpa kartu kredit, tanpa konfigurasi DNS, dan siswa cukup scan QR Code dari smartphone masing-masing.

📱 2. Desain Klien Siswa (Mobile-View First)

Klien permainan untuk siswa dirancang dengan filosofi Mobile-View First:

  1. Zero-Install: Siswa tidak perlu mengunduh aplikasi di Play Store atau App Store. Berjalan mulus di Chrome, Safari, Samsung Internet, Edge pada Android, iOS, Chromebook, dan laptop.
  2. Onboarding Cepat:
    • Masukkan Nama Lengkap.
    • Masukkan Nomor Induk Siswa/Mahasiswa (NIS/NIM) (krusial untuk rekap nilai otomatis).
    • Pilih Regu/Kelompok (jika bermain tim) atau Avatar warna-warni.
  3. Antarmuka Responsif & Ergonomis:
    • Tombol jawaban berukuran besar (fat-finger friendly), meminimalkan salah tekan saat tegang.
    • Tombol Buzzer responsif getar (haptic feedback via navigator.vibrate jika didukung).
    • Status koneksi real-time (ikon hijau jika terhubung, peringatan otomatis jika sinyal terputus).
    • Tampilan umpan balik instan: Animasi konfeti saat benar, getaran halus saat salah, dan poin yang diraih.

📊 3. Buku Nilai Akademik & Rekap Otomatis (Grade Ledger)

Setiap kali siswa menekan tombol atau menyelesaikan putaran board game, server mencatat log transaksi penilaian berpresisi tinggi:

Data yang Dicatat Server:

{
  "sessionId": "GAME-20261027-TKRO-01",
  "gameTitle": "Ular Tangga Pemeliharaan Mesin Otomotif",
  "timestamp": "2026-10-27T10:15:32.410Z",
  "student": {
    "nis": "20241088",
    "name": "Budi Pratama",
    "team": "Tim Piston Alpha"
  },
  "questionId": "Q04",
  "topic": "Sistem Pengapian CDI",
  "cpmkKd": "KD 3.4 / CPMK-02",
  "studentAnswer": "C",
  "correctAnswer": "C",
  "isCorrect": true,
  "responseTimeMs": 3420,
  "pointsEarned": 100,
  "currentStreak": 3
}

Format Ekspor Buku Nilai:

  1. Rekap Nilai Siswa (Rekap_Nilai_Game_[Judul]_[Tanggal].xlsx):
    • Kolom: Peringkat, NIS/NIM, Nama Siswa, Regu, Total Skor, Jawaban Benar, Jawaban Salah, Akurasi (%), Rata-rata Kecepatan Jawab (detik), Nilai Konversi (Skala 100 / Huruf A-E).
  2. Analisis Butir Soal (Item Discrimination & Difficulty):
    • Persentase siswa yang menjawab benar per nomor soal.
    • Identifikasi soal paling sulit (most missed concept) sebagai bahan evaluasi guru/dosen sebelum mengakhiri kelas.
  3. Payload Integrasi Sistem:
    • File JSON siap pakai yang dapat langsung diimpor ke skill student-submission-assessor dan obe-excel-dashboard-builder untuk kalkulasi portofolio ketercapaian CPMK/CPL.

💻 4. Template Server Mandiri Siap Pakai

Berikut adalah implementasi server ringan yang dapat dijalankan langsung di laptop pengajar menggunakan Python atau Node.js:

Opsi A: Server Python Standalone (classroom_server.py)

Hanya memerlukan instalasi Python standar dan library ringan (pip install flask flask-socketio openpyxl netifaces):

"""
Classroom Multiplayer Game Host & Real-Time Assessment Ledger
Jalankan: python classroom_server.py
"""
import socket
import datetime
from flask import Flask, render_template_string, request, jsonify
from flask_socketio import SocketIO, emit, join_room
import openpyxl

app = Flask(__name__)
app.config['SECRET_KEY'] = 'classroom-secret-key-safe-local'
socketio = SocketIO(app, cors_allowed_origins="*")

# In-memory session state
session_data = {
    "title": "Kuis Cerdas Cermat & Board Game",
    "status": "LOBBY", # LOBBY, PLAYING, FINISHED
    "students": {},    # sid -> {name, nis, score, correct_count, answers}
    "logs": []
}

def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(('10.255.255.255', 1))
        IP = s.getsockname()[0]
    except Exception:
        IP = '127.0.0.1'
    finally:
        s.close()
    return IP

@app.route('/')
def student_client():
    # Menampilkan interface kuis Mobile-First untuk siswa
    return render_template_string("""
    <!DOCTYPE html>
    <html lang="id">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
      <title>Kuis Kelas Interaktif</title>
      <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
      <style>
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8fafc; margin: 0; padding: 16px; color: #1e293b; }
        .card { background: white; border-radius: 16px; padding: 20px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); max-width: 420px; margin: 0 auto; }
        h1 { font-size: 20px; text-align: center; color: #0284c7; margin-top: 0; }
        input, button { width: 100%; box-sizing: border-box; padding: 14px; margin-bottom: 12px; border-radius: 10px; font-size: 16px; border: 1px solid #cbd5e1; }
        button { background: #0284c7; color: white; border: none; font-weight: bold; cursor: pointer; }
        .grid-answers { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 15px; }
        .btn-opt { background: #e0f2fe; color: #0369a1; border: 2px solid #bae6fd; font-size: 18px; min-height: 80px; }
        .btn-opt:active { background: #0284c7; color: white; }
        .badge { display: inline-block; padding: 4px 10px; border-radius: 99px; background: #e2e8f0; font-size: 12px; font-weight: bold; }
      </style>
    </head>
    <body>
      <div class="card" id="loginCard">
        <h1>🎮 Masuk Permainan Kelas</h1>
        <input type="text" id="nis" placeholder="Nomor Induk Siswa (NIS/NIM)" required>
        <input type="text" id="name" placeholder="Nama Lengkap Siswa" required>
        <button onclick="joinGame()">Mulai Bermain</button>
      </div>

      <div class="card" id="gameCard" style="display:none;">
        <div style="display:flex; justify-content:space-between; align-items:center;">
          <span id="playerInfo" class="badge">Siswa</span>
          <span id="scoreBadge" class="badge" style="background:#fef08a; color:#854d0e;">Skor: 0</span>
        </div>
        <div id="waitMsg" style="text-align:center; padding: 40px 0;">
          <p style="font-size:18px; color:#64748b;">Menunggu guru memulai pertanyaan...</p>
        </div>
        <div id="quizArea" style="display:none;">
          <h2 id="qText" style="font-size:16px;">Pertanyaan</h2>
          <div class="grid-answers">
            <button class="btn-opt" onclick="sendAnswer('A')">A</button>
            <button class="btn-opt" onclick="sendAnswer('B')">B</button>
            <button class="btn-opt" onclick="sendAnswer('C')">C</button>
            <button class="btn-opt" onclick="sendAnswer('D')">D</button>
          </div>
        </div>
      </div>

      <script>
        const socket = io();
        let studentData = {};

        function joinGame() {
          const nis = document.getElementById('nis').value.trim();
          const name = document.getElementById('name').value.trim();
          if(!nis || !name) return alert('Silakan isi NIS dan Nama!');
          studentData = { nis, name };
          socket.emit('student_join', studentData);
          document.getElementById('loginCard').style.display = 'none';
          document.getElementById('gameCard').style.display = 'block';
          document.getElementById('playerInfo').innerText = name + ' (' + nis + ')';
        }

        socket.on('new_question', (data) => {
          document.getElementById('waitMsg').style.display = 'none';
          document.getElementById('quizArea').style.display = 'block';
          document.getElementById('qText').innerText = data.question;
        });

        function sendAnswer(opt) {
          socket.emit('submit_answer', { ...studentData, answer: opt });
          document.getElementById('quizArea').style.display = 'none';
          document.getElementById('waitMsg').style.display = 'block';
          document.getElementById('waitMsg').innerHTML = '<p style="color:#0284c7; font-weight:bold;">Jawaban ' + opt + ' terkirim! Menunggu hasil...</p>';
        }

        socket.on('update_score', (res) => {
          document.getElementById('scoreBadge').innerText = 'Skor: ' + res.score;
        });
      </script>
    </body>
    </html>
    """)

@app.route('/host')
def host_dashboard():
    # Menampilkan Layar Utama Guru / Proyektor Kelas
    return render_template_string("""
    <!DOCTYPE html>
    <html>
    <head>
      <title>Host Monitor - Layar Proyektor</title>
      <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
      <style>
        body { font-family: sans-serif; background: #0f172a; color: white; padding: 24px; }
        .header { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #334155; padding-bottom: 12px; }
        .leaderboard { width: 100%; border-collapse: collapse; margin-top: 20px; }
        .leaderboard th, .leaderboard td { padding: 12px; text-align: left; border-bottom: 1px solid #1e293b; }
        .leaderboard th { background: #1e293b; color: #38bdf8; }
        .btn { padding: 10px 20px; font-weight: bold; background: #0284c7; color: white; border: none; border-radius: 8px; cursor: pointer; }
      </style>
    </head>
    <body>
      <div class="header">
        <h1>📊 Layar Proyektor & Monitor Guru</h1>
        <div>
          <button class="btn" onclick="startQuestion()">Kirim Soal Berikutnya</button>
          <a href="/export" class="btn" style="background:#16a34a; text-decoration:none;">📥 Download Rekap Nilai Excel</a>
        </div>
      </div>
      <h3>Peserta Terdaftar: <span id="count">0</span> Siswa</h3>
      <table class="leaderboard" id="tbRank">
        <thead>
          <tr>
            <th>Peringkat</th>
            <th>NIS/NIM</th>
            <th>Nama Siswa</th>
            <th>Skor</th>
            <th>Status</th>
          </tr>
        </thead>
        <tbody id="tbodyRank"></tbody>
      </table>
      <script>
        const socket = io();
        socket.on('roster_update', (data) => {
          document.getElementById('count').innerText = data.students.length;
          const tbody = document.getElementById('tbodyRank');
          tbody.innerHTML = '';
          data.students.sort((a,b) => b.score - a.score).forEach((st, idx) => {
            tbody.innerHTML += `<tr>
              <td>#${idx+1}</td>
              <td>${st.nis}</td>
              <td>${st.name}</td>
              <td><b>${st.score}</b></td>
              <td>Aktif</td>
            </tr>`;
          });
        });

        function startQuestion() {
          const qText = prompt("Ketik teks pertanyaan atau klik OK untuk soal default:", "Apa fungsi utama torak (piston) pada motor bakar?");
          if(qText) {
            socket.emit('host_broadcast_question', { question: qText, correct: 'A' });
          }
        }
      </script>
    </body>
    </html>
    """)

@app.route('/export')
def export_excel():
    # Membuat file Excel rekap nilai otomatis
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Rekap Nilai Kuis"
    ws.append(["Peringkat", "NIS/NIM", "Nama Lengkap", "Total Skor", "Akurasi Jawaban", "Status Kelulusan"])
    
    sorted_students = sorted(session_data["students"].values(), key=lambda x: x["score"], reverse=True)
    for idx, st in enumerate(sorted_students, start=1):
        status = "Tuntas" if st["score"] >= 75 else "Remedial"
        ws.append([idx, st["nis"], st["name"], st["score"], f"{st.get('correct', 0)} Benar", status])
    
    filename = f"Rekap_Nilai_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
    wb.save(filename)
    return f"File rekap nilai tersimpan di laptop guru: {filename}"

@socketio.on('student_join')
def handle_join(data):
    session_data["students"][request.sid] = {
        "nis": data["nis"],
        "name": data["name"],
        "score": 0,
        "correct": 0
    }
    emit_roster()

@socketio.on('submit_answer')
def handle_answer(data):
    st = session_data["students"].get(request.sid)
    if st:
        # Penilaian instan: Poin 100 untuk jawaban benar
        st["score"] += 100
        st["correct"] += 1
        emit('update_score', {"score": st["score"]})
        emit_roster()

@socketio.on('host_broadcast_question')
def handle_broadcast(data):
    emit('new_question', data, broadcast=True)

def emit_roster():
    socketio.emit('roster_update', {
        "students": list(session_data["students"].values())
    })

if __name__ == '__main__':
    local_ip = get_local_ip()
    print("=" * 60)
    print("🚀 CLASSROOM MULTIPLAYER GAME HOST AKTIF!")
    print(f"👉 Akses Siswa (LAN): http://{local_ip}:8000")
    print(f"👉 Akses Guru (Proyektor): http://localhost:8000/host")
    print("=" * 60)
    print("💡 Ingin Akses Online Bebas Kuota/Sekolah AP-Isolation?")
    print(f"   Buka terminal baru lalu jalankan:")
    print(f"   npx untun tunnel --port 8000")
    print("=" * 60)
    socketio.run(app, host='0.0.0.0', port=8000)

⚡ 5. Perintah Cepat Menjalankan Tunneling Tanpa Akun

Untuk membagikan kuis ke siswa saat Wi-Fi sekolah terisolasi atau siswa belajar dari rumah:

Opsi 1: Untun (Cloudflare Quick Tunnels - Tanpa Install Software Tambahan)

Cukup jalankan di terminal:

npx untun tunnel --port 8000

Terminal akan menampilkan URL publik: https://xxx-xxx.trycloudflare.com. Siswa dapat langsung membukanya tanpa peringatan sertifikat SSL.

Opsi 2: Localtunnel

npx localtunnel --port 8000

📋 6. Checklist Keamanan Kredensial & Etika Penilaian

  1. Keamanan Kredensial: Server kuis ini dirancang tanpa memerlukan kredensial database cloud, token API rahasia, atau password di sisi klien. Semua data log siswa disimpan lokal di memori server pengajar.
  2. Kerahasiaan Data Siswa: Data nilai tidak diunggah ke pihak ketiga publik; file .xlsx dan .json diunduh langsung ke media penyimpanan lokal laptop guru.
  3. Integritas Penilaian: Kunci jawaban hanya tersimpan di sisi server pengajar (/host), sehingga siswa tidak bisa mengintip jawaban via Inspect Element browser.

🤝 7. Integrasi dengan Ekosistem Course-Agent-Skills

  • Dihasilkan dari game yang dibuat oleh board-game-quiz-maker.
  • Nilai dan log respon diekspor langsung untuk dianalisis oleh student-submission-assessor.
  • Rekap akumulasi skor dimasukkan ke portofolio ketercapaian OBE melalui obe-excel-dashboard-builder.