About classroom-multiplayer-host
A classroom game-hosting skill for teachers running real-time multiplayer quizzes and educational games. It supports offline local networks or instant HTTPS tunnels and records scores for export.
Key features
- 01Offline LAN and hotspot play
- 02Instant HTTPS tunneling
- 03Mobile browser-based student play
- 04Automatic score and response logging
- 05Excel and JSON exports
Use cases
- 01Running buzzer quizzes without internet
- 02Including remote learners in a classroom game
- 03Reviewing student scores and question difficulty
- 04Exporting game results for assessment
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ lensetek/ course-agent-skills --skill classroom-multiplayer-hostAfter installing, type/classroom-multiplayer-hostin Claude Code to confirm it loaded.
Details
From the project READMEClassroom 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:
- Tanpa perlu beli domain publik atau sewa VPS cloud.
- Tanpa konfigurasi rumit port-forwarding pada router sekolah.
- Mendukung mode 100% Offline LAN/Hotspot (hemat kuota, latensi < 10ms).
- Mendukung mode Zero-Config Instant HTTPS Tunneling jika ada siswa daring atau router sekolah mengaktifkan AP Client Isolation.
- 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.xatau172.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 8000ataucloudflared 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.
- Menggunakan Cloudflare Quick Tunnels (
📱 2. Desain Klien Siswa (Mobile-View First)
Klien permainan untuk siswa dirancang dengan filosofi Mobile-View First:
- 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.
- 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.
- Antarmuka Responsif & Ergonomis:
- Tombol jawaban berukuran besar (fat-finger friendly), meminimalkan salah tekan saat tegang.
- Tombol Buzzer responsif getar (haptic feedback via
navigator.vibratejika 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:
- 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).
- 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.
- Payload Integrasi Sistem:
- File JSON siap pakai yang dapat langsung diimpor ke skill
student-submission-assessordanobe-excel-dashboard-builderuntuk kalkulasi portofolio ketercapaian CPMK/CPL.
- File JSON siap pakai yang dapat langsung diimpor ke skill
💻 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
- 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.
- Kerahasiaan Data Siswa: Data nilai tidak diunggah ke pihak ketiga publik; file
.xlsxdan.jsondiunduh langsung ke media penyimpanan lokal laptop guru. - 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.