dev.html: Spionens skærme i alle faser

?game=spion viser storskærmen og tre telefoner (en agent, spionen og
publikum) i alle faser, også med rollekortet vist (clue-open, vote), dommen
med stempel og ved lighed, og resultatet med to spioner. Telefonernes data
er som serverens: ingen stikord i clues, intet svar under dommen.
spion.js kan vise kortet uden tryk (kun til harnessen). Stemmesøjlerne er
lige lange, også når en spiller har publikumsstemmer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
agw
2026-10-20 21:59:56 +02:00
co-authored by Claude Opus 5.5
parent 3ec45ca66b
commit 78fa557da4
4 changed files with 136 additions and 6 deletions
+9 -4
View File
@@ -40,17 +40,19 @@
<script src="common.js"></script>
<script src="tegn.js"></script>
<script src="menu.js"></script>
<script src="spion.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 src="spion-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); ?game=menu: spilmenuen */
/* ?game=tegn / ?game=spion: det spils skærme (samme storskærm/telefon-kode, egne fixtures); ?game=menu: spilmenuen */
var MENU = {
phases: ['menu', 'intro-fof', 'intro-tegn', 'skift-menu', 'skift-intro'],
build: function (p) {
@@ -59,7 +61,7 @@
return { menu: key ? { screen: 'intro', key: key, values: {}, sw: sw } : { screen: 'menu', sw: sw }, phones: [] };
}
};
var FX = { tegn: WPPTegnFixtures, menu: MENU }[qs.get('game')] || WPPFixtures;
var FX = { tegn: WPPTegnFixtures, spion: WPPSpionFixtures, menu: MENU }[qs.get('game')] || WPPFixtures;
if (FX === MENU) { phase = MENU.phases.indexOf(phase) >= 0 ? phase : 'menu'; document.body.classList.add('only-host-panel'); }
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'; });
@@ -85,14 +87,17 @@
}, onJoin: function (c, n) { log({ action: 'join', code: c, name: n }); } };
if (ph.join) WPPPlay.renderJoin(root, ctx, ph.join);
else WPPPlay.renderPlay(root, ph.state, ctx);
/* Spionen: rollekortet vist (ellers er det skjult, til man trykker) */
var GU = WPP.gameUI(ph.state && ph.state.game);
if (ph.open && GU && GU.openCard) GU.openCard(root, ph.state);
/* "tease"-fasen: drilleriet bliver stående (ellers lukker det efter 2,5 s) */
if (ph.tease) WPPPlay.showTease(root, ph.tease, { ttl: 600000, silent: qs.get('sound') !== '1' });
});
}
['fof', 'tegn', 'menu'].forEach(function (g) {
['fof', 'tegn', 'spion', 'menu'].forEach(function (g) {
var a = document.createElement('a');
a.href = '?game=' + g; a.textContent = { fof: 'Fup og Fakta', tegn: 'Tegn og Gæt', menu: 'Menu' }[g];
a.href = '?game=' + g; a.textContent = { fof: 'Fup og Fakta', tegn: 'Tegn og Gæt', spion: 'Spionen', menu: 'Menu' }[g];
bar.appendChild(a);
});
FX.phases.forEach(function (p) {
+1 -1
View File
@@ -1088,7 +1088,7 @@ button, input { font-family: inherit; }
.sp-timer { display: flex; justify-content: center; }
.sp-timer.big .ring { width: 9em; height: 9em; }
.sp-bars { list-style: none; margin: 0 auto; padding: 0; display: grid; gap: 10px; width: min(900px, 92%); }
.sp-bars li { display: grid; grid-template-columns: 9em 1fr 3em auto; align-items: center; gap: 14px; font-size: 1.4em; animation: mn-in .4s ease both;
.sp-bars li { display: grid; grid-template-columns: 9em 1fr 3em 7em; align-items: center; gap: 14px; font-size: 1.4em; animation: mn-in .4s ease both;
animation-delay: calc(var(--i) * 120ms); }
.sp-bars .bar { height: 22px; border-radius: 11px; background: var(--faint); overflow: hidden; }
.sp-bars .bar i { display: block; height: 100%; width: calc(var(--w) * 100%); background: var(--cyan); border-radius: 11px; }
+118
View File
@@ -0,0 +1,118 @@
/* Fixture-snapshots for Spionen til dev.html?game=spion (ingen server). window.WPPSpionFixtures.build(phase, opts) */
(function () {
'use strict';
var COLORS = ['#ff4d6d', '#4cc9f0', '#ffd166', '#06d6a0', '#b388ff', '#ff9f1c'];
var NAMES = ['Asger', 'Sus', 'Bo', 'Mette', 'Kasper', 'Line'];
var PHASES = ['lobby', 'rules', 'clue', 'clue-open', 'clues', 'talk', 'vote', 'verdict', 'verdict-stamp', 'verdict-tie',
'guess', 'result', 'result-two', 'final'];
var REAL = { 'clue-open': 'clue', 'verdict-stamp': 'verdict', 'verdict-tie': 'verdict', 'result-two': 'result' };
var CLUES = ['vinger', 'luft', 'rejse', 'hurtig', 'sky', 'motor'];
function players(n, done) {
var out = [];
for (var i = 0; i < n; i++) {
out.push({ id: 'p' + (i + 1), name: NAMES[i], color: COLORS[i], score: [400, 100, 300, 0, 200, 100][i], connected: true,
vip: i === 0, done: !!(done && done[i]), in_round: true });
}
return out;
}
function build(phase, opts) {
opts = opts || {};
var now = Date.now(), real = REAL[phase] || phase;
var host = { game: 'spion', code: 'KLMT', phase: real, server_time: now, deadline: null, join_url: 'https://party.weircon.dk/?code=KLMT',
min_players: 4, max_players: 10, round: null, players: players(6), audience_count: 1, fest: false,
reactions: ['😂', '😱', '👏', '🤯', '🙈', '🔥'], host: true, phase_key: phase };
if (real !== 'lobby' && real !== 'final' && real !== 'rules') {
host.round = { number: 2, of: 4, name: 'Runde 2 af 4' };
host.theme = { name: 'Transport', icon: '🚂' };
host.spies = phase === 'result-two' ? 2 : 1;
}
var items = CLUES.map(function (c, i) { return { player_id: 'p' + ((i * 5) % 6 + 1), text: i === 4 ? null : c }; });
var votes = { counts: [{ player_id: 'p2', votes: 4 }, { player_id: 'p3', votes: 1 }, { player_id: 'p1', votes: 1 },
{ player_id: 'p4', votes: 0 }, { player_id: 'p5', votes: 0 }, { player_id: 'p6', votes: 0 }],
audience: [{ player_id: 'p2', votes: 1 }], by: [], accused: 'p2', accused_is_spy: true };
switch (phase) {
case 'rules':
host.rules = { items: [
{ icon: '🕵️', title: 'Et hemmeligt ord', body: 'Alle ser det samme ord på telefonen. Spionen ser kun temaet.' },
{ icon: '✏️', title: 'Ét stikord', body: 'Skriv ét ord, der viser, at du kender ordet. Uden at afsløre det.' },
{ icon: '🗣️', title: 'Snak sammen', body: 'Stikordene kommer på skærmen. Så har I et minut til at snakke.' },
{ icon: '🗳️', title: 'Stem', body: 'Stem på den, I tror er spionen. Lige mange stemmer: spionen slipper.' },
{ icon: '🎯', title: 'Sidste chance', body: 'Bliver spionen anklaget, kan den stadig redde sig ved at gætte ordet.' }],
started_at: now - 7000, steps_ms: [5000, 5000, 5000, 5000, 5000], audio: null };
host.deadline = now + 19000;
break;
case 'clue':
case 'clue-open':
host.deadline = now + 31000;
host.players = players(6, [1, 0, 1, 1]);
break;
case 'clues':
host.clues = { started_at: now - (opts.t != null ? Number(opts.t) : 9000), intro_ms: 2000, clue_ms: 2600, items: items };
host.deadline = now + 9000;
break;
case 'talk':
host.clues = { items: items };
host.deadline = now + 42000;
break;
case 'vote':
host.clues = { items: items };
host.deadline = now + 20000;
host.players = players(6, [1, 1, 0, 1, 0, 1]);
break;
case 'verdict':
case 'verdict-stamp':
case 'verdict-tie':
host.clues = { items: items };
host.votes = phase === 'verdict-tie' ? Object.assign({}, votes, { accused: null, accused_is_spy: false,
counts: votes.counts.map(function (c, i) { return { player_id: c.player_id, votes: i < 2 ? 2 : 0 }; }) }) : votes;
host.deadline = now + (phase === 'verdict-stamp' ? 1500 : 6000);
break;
case 'guess':
host.clues = { items: items };
host.votes = votes;
host.deadline = now + 24000;
break;
case 'result':
case 'result-two':
host.clues = { items: items };
host.votes = votes;
var two = phase === 'result-two';
host.result = { spies: two ? ['p2', 'p5'] : ['p2'], spy_names: two ? ['Sus', 'Kasper'] : ['Sus'], word: 'flyvemaskine',
caught: 'p2', guess: { text: 'helikopter', correct: false }, spy_won: false,
deltas: two ? { p1: 100, p3: 100, p4: 0, p5: 300, p6: 100, p2: 0 } : { p1: 100, p3: 100, p4: 100, p5: 100, p6: 0, p2: 0 } };
host.deadline = now + 8000;
break;
case 'final':
host.final = { ranking: [{ player_id: 'p1', score: 900, place: 1 }, { player_id: 'p3', score: 700, place: 2 },
{ player_id: 'p2', score: 600, place: 3 }, { player_id: 'p5', score: 400, place: 4 }, { player_id: 'p6', score: 200, place: 5 },
{ player_id: 'p4', score: 100, place: 6 }],
awards: [{ title: 'Bedste løgner', player_id: 'p2', detail: '600 point som spion', kind: 'liar' },
{ title: 'Skarpeste øje', player_id: 'p1', detail: 'stemte rigtigt 4 gange', kind: 'sharp' },
{ title: 'Mest mistænkt', player_id: 'p4', detail: '9 stemmer i alt', kind: 'suspected' }] };
break;
}
/* tre telefoner: en agent (VIP), spionen og publikum */
var inRound = ['clue', 'clues', 'talk', 'vote', 'verdict', 'guess', 'result'].indexOf(real) >= 0;
var mes = [
{ id: 'p1', name: 'Asger', role: 'player', vip: true, score: 400, card: inRound ? { spy: false, theme: 'Transport', word: 'flyvemaskine' } : null,
clue: real === 'clue' ? 'vinger' : null, vote: real === 'vote' ? 'p2' : null, can_guess: false },
{ id: 'p2', name: 'Sus', role: 'player', vip: false, score: 100, card: inRound ? { spy: true, theme: 'Transport', word: null } : null,
clue: null, vote: null, can_guess: real === 'guess' },
{ id: 'u7', name: 'Lis', role: 'audience', vip: false, score: 0, card: null, clue: null, vote: null, can_guess: false }
];
var phones = mes.map(function (me) {
var st = JSON.parse(JSON.stringify(host));
delete st.host; delete st.phase_key;
if (st.clues && real === 'clues') st.clues.items = [];
if (st.votes && real === 'verdict') st.votes.accused_is_spy = false; /* telefonerne får det først i "guess" */
st.me = me;
return { label: me.name + (me.role === 'audience' ? ' (publikum)' : me.card && me.card.spy ? ' (spion)' : me.vip ? ' (VIP)' : ''),
state: st, open: phase === 'clue-open' || real === 'vote' };
});
return { host: host, phones: phones };
}
window.WPPSpionFixtures = { build: build, phases: PHASES };
})();
+8 -1
View File
@@ -216,9 +216,16 @@
}
function afterHost() { /* */ }
/* dev.html: vis rollekortet uden et tryk */
function openCard(root, state) {
var u = root._ui || (root._ui = {});
sp(u).shown = state.code + ':' + (state.round || {}).number + ':' + state.phase;
if (window.WPPPlay) WPPPlay.renderPlay(root, state, root._ctx);
}
window.WPPGameUI = window.WPPGameUI || {};
window.WPPGameUI.spion = {
play: play, host: host, afterPlay: afterPlay, afterHost: afterHost, playStale: playStale, markPlay: markPlay,
hostTick: hostTick, doneChips: { clue: 1, vote: 1 }, timed: { clue: 1, clues: 1, talk: 1, vote: 1, verdict: 1, result: 1 }
hostTick: hostTick, openCard: openCard, doneChips: { clue: 1, vote: 1 }, timed: { clue: 1, clues: 1, talk: 1, vote: 1, verdict: 1, result: 1 }
};
})();