Tegn og Gæt: tegn dit eget ord på telefon, storskærm og speaker
CI / test (push) Successful in 1m25s

Telefonen og storskærmen siger "Tegn dit eget ord" i trin 1 ved et
lige antal spillere, og speakeren har fem faste replikker til det
(forvarmes med de andre faste). dev.html?game=tegn har fasen draw-own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
agw
2026-10-20 21:59:59 +02:00
co-authored by Claude Opus 5.5
parent 5de261acd7
commit d22f1d5e2e
6 changed files with 27 additions and 8 deletions
+9 -2
View File
@@ -3,8 +3,8 @@
'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 PHASES = ['lobby', 'rules', 'theme', 'word', 'draw', 'draw-own', 'guess', 'reveal-wait', 'reveal', 'reveal-guess', 'reveal-end', 'final'];
var REAL = { 'draw-own': 'draw', 'reveal-wait': 'reveal', 'reveal-guess': 'reveal', 'reveal-end': 'reveal' };
var INK = ['#1d1b2e', '#e63946', '#2a7de1', '#2bb673'];
function circle(cx, cy, r, n) {
@@ -119,6 +119,13 @@
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 'draw-own': /* lige antal spillere: trin 1 tegner ejeren sit eget ord */
host.theme = theme; host.step = { index: 1, number: 2, of: 5, kind: 'draw', own: true }; host.deadline = now + 61000;
host.players = players(4, [{}, { done: true }]);
mes[0].task = { kind: 'draw', done: false, from: 'p1', text: 'kat', drawing: null, mine: null, draft: null };
mes[1].task = { kind: 'draw', done: true, from: 'p2', text: 'pizza', drawing: null, mine: null, draft: null };
mes[2].task = { kind: 'draw', done: false, from: 'p3', text: 'giraf', 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 };
+4 -3
View File
@@ -163,7 +163,7 @@
var out = '<p class="hint center" data-k="sl">' + stepLabel(state) + '</p>';
if (!t) return out + big('Spillerne tegner…', 'Kig med på storskærmen bagefter.', 'aud', 'pulse');
if (t.done) return out + big('Flot tegnet!', 'Venter på de andre…', 'done', 'pop') + doneCount(state);
var s = tg(u), full = s.full;
var s = tg(u), full = s.full, own = !!(state.step && state.step.own);
var tools = PALETTE.map(function (c) {
return '<button type="button" class="tg-sw' + (s.color === c ? ' on' : '') + '" style="--sw:' + c + '" data-tact="color" data-arg="' + c + '" aria-label="Farve"></button>';
}).join('') + WIDTHS.map(function (w, i) {
@@ -171,7 +171,7 @@
}).join('') +
'<button type="button" class="tg-tool" data-tact="undo" aria-label="Fortryd">↶</button>' +
'<button type="button" class="tg-tool" data-tact="clear" aria-label="Ryd">🗑</button>';
return out + '<section class="tg-prompt" data-k="pr:' + esc(t.text || '') + '"><span>Tegn</span><b>' + esc(t.text || '') + '</b></section>' +
return out + '<section class="tg-prompt" data-k="pr:' + (own ? 'own:' : '') + esc(t.text || '') + '"><span>' + (own ? 'Tegn dit eget ord' : 'Tegn') + '</span><b>' + esc(t.text || '') + '</b></section>' +
'<div class="tg-pad" data-k="pad:' + esc(stepKey(state)) + '"><canvas id="tg-draw" class="tg-canvas" aria-label="Tegneflade"></canvas></div>' +
'<section class="tg-tools" data-k="tools:' + s.color + s.width + '">' + tools + '</section>' +
(full ? '<p class="hint center" data-k="full">Tegningen er fuld. Send den, eller fortryd lidt.</p>' : '') +
@@ -356,6 +356,7 @@
var KIND_TEXT = {
word: 'Skriv et <b>ord</b> om temaet på din telefon!',
draw: '<b>Tegn</b> det ord, du har fået!',
own: '<b>Tegn</b> dit eget ord!',
guess: 'Hvad forestiller <b>tegningen</b>? Skriv dit gæt!'
};
var host = {};
@@ -377,7 +378,7 @@
'<div class="tg-kicker">' + esc(stepLabel(state)) + ' · ' + esc((t.icon ? t.icon + ' ' : '') + (t.name || '')) + '</div>' +
'<div class="tg-bigicon">' + KIND_ICON[kind] + '</div>' +
'<h1 class="tg-steptitle">' + ({ word: 'Skriv et ord', draw: 'Tegn!', guess: 'Gæt tegningen' })[kind] + '</h1>' +
'<p class="instr">' + KIND_TEXT[kind] + '</p></section>' +
'<p class="instr">' + KIND_TEXT[state.step && state.step.own ? 'own' : kind] + '</p></section>' +
'<div class="hs-timer" data-k="tm:' + (state.deadline || 0) + '">' + WPP.timerHtml(state, 'ring') + '</div>';
}
host.word = host.draw = host.guess = stepScreen;
+2 -2
View File
@@ -30,7 +30,7 @@ BEAT_PAD_MS = 700 # luft efter speakerens replik i et trin af afsløringe
HURRY_BEFORE = 10 # "ti sekunder tilbage"
# Replikker uden navne eller spillertekst, som rummet siger alene: dem kan tts_warm lave på forhånd.
# reveal_intro er udeladt med vilje: den siges kun sammen med første kædes intro, som har et navn.
FIXED_LINES = ("lobby_welcome", "draw_start", "guess_start", "step_hurry", "step_all_done", "play_again")
FIXED_LINES = ("lobby_welcome", "draw_start", "draw_own_start", "guess_start", "step_hurry", "step_all_done", "play_again")
THEME_LINES = ("theme_chosen", "theme_timeout", "word_start") # {tema}: ét sæt pr. tema
COUNT_LINES = ("game_start",) # {antal_spillere}: ét sæt pr. antal spillere
AWARD_LINES = {"drawing": "award_best_drawing", "guess": "award_best_guess", "survived": "award_survived",
@@ -161,7 +161,7 @@ class TegnRoom(rooms.BaseRoom):
if t == "step":
if ev["step"] == "word":
return [line("word_start", tema=ev["theme"])]
return [line(f"{ev['step']}_start")]
return [line("draw_own_start" if ev.get("own") else f"{ev['step']}_start")]
if t in ("step_all_done", "play_again"):
return [line(t)]
if t == "final":
+4
View File
@@ -187,6 +187,10 @@ class TegnSpeakerTests(SimpleTestCase):
room = TegnRoom("TEST", g, "agw")
self.assertIn("Dyr", room.lines_for({"type": "theme_chosen", "theme": "Dyr", "timeout": False})[0])
self.assertIn("Dyr", room.lines_for({"type": "step", "step": "word", "theme": "Dyr"})[0])
own = room.lines_for({"type": "step", "step": "draw", "theme": "Dyr", "own": True})[0]
self.assertIn(own, room.narrator.variants("draw_own_start")) # lige antal: ejeren tegner sit eget ord
self.assertIn(room.lines_for({"type": "step", "step": "draw", "theme": "Dyr", "own": False})[0],
room.narrator.variants("draw_start"))
lines = room.lines_for({"type": "final", "winner": "Bo", "points": 600,
"awards": [{"kind": "drawing", "name": "Sus"}]})
self.assertIn("seks hundrede", lines[0])
+7
View File
@@ -64,6 +64,13 @@
"Tegn løs. Det behøver ikke være kønt, bare genkendeligt.",
"Fingrene frem. Tegn det, du ser på skærmen."
],
"draw_own_start": [
"Tegn dit eget ord. Ingen bogstaver!",
"[excited] Først tegner I jeres eget ord. Kun streger.",
"Nu skal I tegne det ord, I selv skrev. Gør det tydeligt.",
"Jeres eget ord først. Tegn det, så de andre kan gætte det.",
"Tegn det ord, du selv fandt på. Bare streger, ingen bogstaver."
],
"guess_start": [
"Hvad forestiller tegningen? Skriv dit gæt.",
"Kig godt på tegningen. Hvad er det?",
+1 -1
View File
@@ -14,7 +14,7 @@ LINES = Path(__file__).resolve().parents[1] / "narrator" / "lines.json"
EVENTS = {
"lobby_welcome": set(), "lobby_join": {"navn"}, "lobby_waiting": {"vip"}, "game_start": {"antal_spillere"},
"theme_choose": {"vip"}, "theme_chosen": {"tema"}, "theme_timeout": {"tema"}, "word_start": {"tema"},
"draw_start": set(), "guess_start": set(), "step_hurry": set(), "step_all_done": set(),
"draw_start": set(), "draw_own_start": set(), "guess_start": set(), "step_hurry": set(), "step_all_done": set(),
"reveal_intro": set(), "chain_intro": {"navn"}, "chain_survived": {"ord"}, "chain_changed": {"ord", "gaet"},
"award_best_drawing": {"navn"}, "award_best_guess": {"navn"}, "award_survived": {"navn"},
"award_transformed": {"navn"}, "winner": {"vinder", "point"}, "play_again": set(),