Dev-harness: Tegn og Gæts skærme med ?game=tegn

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
agw
2026-10-20 17:33:07 +02:00
co-authored by Claude Opus 5.5
parent d8c4836e65
commit 6c60d56409
2 changed files with 168 additions and 2 deletions
+7 -2
View File
@@ -35,15 +35,19 @@
</div>
<script>window.WPP_HARNESS = true;</script>
<script src="common.js"></script>
<script src="tegn.js"></script>
<script src="host.js"></script>
<script src="play.js"></script>
<script src="dev-fixtures.js"></script>
<script src="tegn-fixtures.js"></script>
<script>
(function () {
var qs = new URLSearchParams(location.search);
var phase = qs.get('phase') || 'lobby';
var t = qs.get('t');
var only = qs.get('only');
/* ?game=tegn: Tegn og Gæts skærme (samme storskærm/telefon-kode, egne fixtures) */
var FX = qs.get('game') === 'tegn' ? WPPTegnFixtures : WPPFixtures;
if (only) document.body.classList.add('only-' + only);
if (qs.get('pw')) Array.prototype.forEach.call(document.querySelectorAll('.phonebox'), function (e) { e.style.width = qs.get('pw') + 'px'; });
var bar = document.getElementById('bar');
@@ -52,9 +56,10 @@
var cur = null, hostCtx = null;
function draw() {
var fx = cur = WPPFixtures.build(phase, { t: t, fest: qs.get("fest") === "1" });
var fx = cur = FX.build(phase, { t: t, fest: qs.get("fest") === "1" });
var hostRoot = document.getElementById('hostroot');
hostCtx = { harness: true, sound: qs.get('sound') === '1', send: log, qrSrc: WPPFixtures.qr };
if (fx.drawings) WPPTegn.setDrawings(hostRoot, fx.drawings);
WPPHost.renderHost(hostRoot, fx.host, hostCtx);
fx.phones.forEach(function (ph, i) {
var root = document.getElementById('ph' + i);
@@ -71,7 +76,7 @@
});
}
WPPFixtures.phases.forEach(function (p) {
FX.phases.forEach(function (p) {
var b = document.createElement('button');
b.textContent = p; if (p === phase) b.className = 'on';
b.onclick = function () {
+161
View File
@@ -0,0 +1,161 @@
/* Fixture-snapshots for Tegn og Gæt til dev.html?game=tegn (ingen server). window.WPPTegnFixtures.build(phase, opts) */
(function () {
'use strict';
var COLORS = ['#ff4d6d', '#4cc9f0', '#ffd166', '#06d6a0', '#b388ff'];
var NAMES = ['Asger', 'Sus', 'Bo', 'Mette', 'Kasper'];
var PHASES = ['lobby', 'rules', 'theme', 'word', 'draw', 'guess', 'reveal-wait', 'reveal', 'reveal-guess', 'reveal-end', 'final'];
var REAL = { 'reveal-wait': 'reveal', 'reveal-guess': 'reveal', 'reveal-end': 'reveal' };
var INK = ['#1d1b2e', '#e63946', '#2a7de1', '#2bb673'];
function circle(cx, cy, r, n) {
var pts = [];
for (var i = 0; i <= n; i++) { var a = i / n * Math.PI * 2; pts.push([+(cx + Math.cos(a) * r).toFixed(3), +(cy + Math.sin(a) * r * 4 / 3).toFixed(3)]); }
return pts;
}
function line(a, b) { return [a, b]; }
var CAT = [
{ color: INK[0], width: 0.012, points: circle(0.5, 0.55, 0.22, 40) },
{ color: INK[0], width: 0.012, points: [[0.33, 0.36], [0.36, 0.12], [0.45, 0.28]] },
{ color: INK[0], width: 0.012, points: [[0.55, 0.28], [0.64, 0.12], [0.67, 0.36]] },
{ color: INK[2], width: 0.024, points: [[0.42, 0.5]] },
{ color: INK[2], width: 0.024, points: [[0.58, 0.5]] },
{ color: INK[1], width: 0.012, points: [[0.47, 0.6], [0.5, 0.64], [0.53, 0.6]] },
{ color: INK[0], width: 0.008, points: line([0.4, 0.62], [0.2, 0.58]) },
{ color: INK[0], width: 0.008, points: line([0.4, 0.66], [0.2, 0.7]) },
{ color: INK[0], width: 0.008, points: line([0.6, 0.62], [0.8, 0.58]) },
{ color: INK[0], width: 0.008, points: line([0.6, 0.66], [0.8, 0.7]) }
];
var DOG = [
{ color: INK[0], width: 0.012, points: circle(0.5, 0.5, 0.2, 40) },
{ color: INK[0], width: 0.024, points: [[0.3, 0.32], [0.24, 0.6]] },
{ color: INK[0], width: 0.024, points: [[0.7, 0.32], [0.76, 0.6]] },
{ color: INK[0], width: 0.024, points: [[0.44, 0.46]] },
{ color: INK[0], width: 0.024, points: [[0.56, 0.46]] },
{ color: INK[1], width: 0.024, points: [[0.48, 0.66], [0.5, 0.74], [0.52, 0.66]] }
];
var SOFA = [
{ color: INK[3], width: 0.024, points: [[0.15, 0.45], [0.15, 0.75], [0.85, 0.75], [0.85, 0.45]] },
{ color: INK[3], width: 0.024, points: [[0.22, 0.6], [0.22, 0.3], [0.78, 0.3], [0.78, 0.6]] },
{ color: INK[0], width: 0.012, points: [[0.2, 0.75], [0.2, 0.85]] },
{ color: INK[0], width: 0.012, points: [[0.8, 0.75], [0.8, 0.85]] }
];
function players(n, extra) {
var out = [];
for (var i = 0; i < n; i++) out.push({ id: 'p' + (i + 1), name: NAMES[i], color: COLORS[i], score: 0, connected: true, vip: i === 0, done: false });
(extra || []).forEach(function (f, i) { if (out[i]) Object.assign(out[i], f); });
return out;
}
function clone(o) { return JSON.parse(JSON.stringify(o)); }
var THEMES = [['dyr', 'Dyr', '🐘'], ['film', 'Film og serier', '🎬'], ['koekken', 'Ting i køkkenet', '🍳'], ['jul', 'Jul', '🎄'], ['sport', 'Sport', '⚽'],
['mad', 'Mad', '🍕'], ['strand', 'På stranden', '🏖️'], ['job', 'Job', '👩‍🚒'], ['skole', 'I skolen', '🏫'], ['eventyr', 'Eventyr', '🏰'],
['transport', 'Transport', '🚂'], ['musik', 'Musik', '🎸'], ['danmark', 'Danmark', '🇩🇰'], ['rummet', 'Rummet', '🚀']]
.map(function (t) { return { id: t[0], name: t[1], icon: t[2] }; });
/* fem kæder à fem bidrag: ord, tegning, gæt, tegning, gæt */
function chains() {
var words = ['kat', 'pizza', 'rumraket', 'juletræ', 'giraf'];
var out = [], drawings = {};
for (var k = 0; k < 5; k++) {
var e = [];
for (var t = 0; t < 5; t++) {
var by = 'p' + (((k + t) % 5) + 1), id = 'c' + k + 'e' + t;
var kind = t === 0 ? 'word' : t % 2 ? 'drawing' : 'guess';
var text = kind === 'drawing' ? null : t === 0 ? words[k] : k === 0 ? (t === 2 ? 'hund' : 'sofa') : k === 1 ? 'pizza' : t === 2 ? 'blomst' : 'paraply';
e.push({ id: id, kind: kind, by: by, text: text, auto: k === 3 && t === 4, likes: { c0e1: 2, c0e2: 1, c0e3: 3 }[id] || 0 });
if (kind === 'drawing') drawings[id] = k === 0 ? (t === 1 ? CAT : DOG) : k === 1 ? SOFA : DOG;
}
out.push({ owner: 'p' + (k + 1), survived: k === 1, entries: e });
}
return { chains: out, drawings: drawings };
}
function plan(chs) {
var out = [];
chs.forEach(function (c, k) {
out.push({ chain: k, entry: -1, ms: 2500, audio: null });
c.entries.forEach(function (e, i) { out.push({ chain: k, entry: i, ms: { word: 3000, drawing: 5500, guess: 3000 }[e.kind], audio: null }); });
out.push({ chain: k, entry: c.entries.length, ms: 4500, audio: null });
});
return out;
}
function build(phase, opts) {
opts = opts || {};
var now = Date.now();
var real = REAL[phase] || phase;
var host = { game: 'tegn', code: 'KLMT', phase: real, server_time: now, deadline: null, join_url: 'https://party.weircon.dk/?code=KLMT',
min_players: 3, max_players: 10, round: null, players: players(5), audience_count: 1, fest: false,
reactions: ['😂', '😱', '👏', '🤯', '🙈', '🔥'], host: true, phase_key: phase };
var drawings = {};
var mes = [0, 1, 2].map(function (i) { return { id: 'p' + (i + 1), name: NAMES[i], role: 'player', vip: i === 0, score: 0, task: null, likes: [], can_choose: false }; });
var theme = { name: 'Dyr', icon: '🐘' };
switch (phase) {
case 'rules':
host.rules = { items: [
{ icon: '✏️', title: 'Skriv et ord', body: 'Alle skriver et ord eller en kort frase inden for temaet.' },
{ icon: '🎨', title: 'Tegn det', body: 'Telefonen sender ordet videre. Den næste tegner det.' },
{ icon: '🤔', title: 'Gæt tegningen', body: 'Den næste ser kun tegningen og skriver, hvad den forestiller.' },
{ icon: '🔁', title: 'Hele vejen rundt', body: 'Skiftevis tegn og gæt, til kæden har været hos alle.' },
{ icon: '❤️', title: 'Hjerter og priser', body: 'Giv hjerter til de bedste tegninger og gæt. Point og priser til sidst.' }],
started_at: now - 7000, steps_ms: [5000, 5000, 5000, 5000, 5000], audio: null };
host.deadline = now + 19000;
break;
case 'theme':
host.theme = { options: THEMES, chosen_id: null };
host.deadline = now + 14000;
mes[0].can_choose = true;
break;
case 'word':
host.theme = theme; host.step = { index: 0, number: 1, of: 5, kind: 'word' }; host.deadline = now + 22000;
host.players = players(5, [{ done: true }, {}, {}, { done: true }]);
mes[0].task = { kind: 'word', done: true, from: null, text: null, drawing: null, mine: 'kat', draft: null };
mes[1].task = { kind: 'word', done: false, from: null, text: null, drawing: null, mine: null, draft: null };
mes[2] = { id: 'u7', name: 'Lis', role: 'audience', vip: false, score: 0, task: null, likes: [], can_choose: false };
break;
case 'draw':
host.theme = theme; host.step = { index: 1, number: 2, of: 5, kind: 'draw' }; host.deadline = now + 61000;
host.players = players(5, [{}, { done: true }]);
mes[0].task = { kind: 'draw', done: false, from: 'p5', text: 'giraf', drawing: null, mine: null, draft: null };
mes[1].task = { kind: 'draw', done: true, from: 'p1', text: 'kat', drawing: null, mine: null, draft: null };
mes[2].task = { kind: 'draw', done: false, from: 'p2', text: 'pizza', drawing: null, mine: null, draft: CAT.slice(0, 4) };
break;
case 'guess':
host.theme = theme; host.step = { index: 2, number: 3, of: 5, kind: 'guess' }; host.deadline = now + 18000;
mes[0].task = { kind: 'guess', done: false, from: 'p4', text: null, drawing: DOG, mine: null, draft: null };
mes[1].task = { kind: 'guess', done: true, from: 'p5', text: null, drawing: CAT, mine: 'hund', draft: null };
mes[2].task = { kind: 'guess', done: false, from: 'p1', text: null, drawing: SOFA, mine: null, draft: 'so' };
break;
case 'reveal-wait':
case 'reveal':
case 'reveal-guess':
case 'reveal-end':
var c = chains();
drawings = c.drawings;
host.theme = theme;
var t = opts.t != null ? Number(opts.t) : { reveal: 7000, 'reveal-guess': 12000, 'reveal-end': 23000 }[phase] || 0;
host.reveal = { id: 1, started_at: phase === 'reveal-wait' ? null : now - t, plan: plan(c.chains), chains: c.chains };
host.deadline = now + 120000;
mes[0].likes = ['c0e3'];
break;
case 'final':
host.players = players(5, [{ score: 800 }, { score: 1100 }, { score: 300 }, { score: 500 }, { score: 0 }]);
host.final = { ranking: [{ player_id: 'p2', score: 1100, place: 1 }, { player_id: 'p1', score: 800, place: 2 }, { player_id: 'p4', score: 500, place: 3 },
{ player_id: 'p3', score: 300, place: 4 }, { player_id: 'p5', score: 0, place: 5 }],
awards: [{ title: 'Bedste tegning', player_id: 'p2', detail: '3 hjerter', kind: 'drawing' },
{ title: 'Bedste gæt', player_id: 'p3', detail: '"sofa", 2 hjerter', kind: 'guess' },
{ title: 'Kæden der overlevede', player_id: 'p2', detail: '"pizza" hele vejen rundt', kind: 'survived' },
{ title: 'Største forvandling', player_id: 'p1', detail: '"kat" blev til "sofa"', kind: 'transformed' }] };
mes.forEach(function (m) { m.score = { p1: 800, p2: 1100, p3: 300 }[m.id]; });
break;
}
var phones = mes.map(function (me) {
var st = clone(host);
delete st.host; delete st.phase_key;
st.me = me;
return { label: me.name + (me.role === 'audience' ? ' (publikum)' : me.vip ? ' (VIP)' : ''), state: st };
});
return { host: host, phones: phones, drawings: { reveal_id: 1, drawings: drawings } };
}
window.WPPTegnFixtures = { build: build, phases: PHASES, qr: null };
})();