-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreference-game-hawkins.html
More file actions
215 lines (201 loc) · 10.9 KB
/
Copy pathreference-game-hawkins.html
File metadata and controls
215 lines (201 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<!DOCTYPE html>
<html>
<head>
<title>Reference Game — Hawkins et al. 2020 replication (sequential) — jsPsych multiplayer</title>
<!-- jsPsych core MUST carry the multiplayer API (jsPsych#3694). SHA-pinned to the #3694 preview
build (see examples/README.md "Running it" to re-pin). -->
<script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@151ab520542a8e48bcc4d5b21c74cdffae8b48c6/packages/jspsych/dist/index.browser.min.js"></script>
<script src="https://unpkg.com/@jspsych/plugin-survey-text"></script>
<script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response"></script>
<script src="https://unpkg.com/@jspsych/plugin-preload"></script>
<!-- STUDY PROTOTYPE: on the LOCAL adapter for two-tab piloting on one machine. For the paid Prolific
run, swap adapter-multiplayer-local → adapter-multiplayer-firebase (and pair via a real waiting
room); nothing else in this file changes. -->
<!-- TODO: pin to a real published version once jspsych-multiplayer PR #35 (Version Packages) merges -->
<script src="https://cdn.jsdelivr.net/npm/@jspsych-multiplayer/adapter-multiplayer-local@0.1.0/dist/index.browser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@jspsych-multiplayer/plugin-multiplayer-sync@0.1.0/dist/index.browser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@jspsych-multiplayer/plugin-multiplayer-role@0.1.0/dist/index.browser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@jspsych-multiplayer/plugin-multiplayer-reference-game@0.1.0/dist/index.browser.min.js"></script>
<script src="tangrams.js"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@151ab520542a8e48bcc4d5b21c74cdffae8b48c6/packages/jspsych/css/jspsych.css"
/>
</head>
<body></body>
<script>
// ===============================================================================================
// REFERENCE GAME — replication of Hawkins, Frank & Goodman (2020), "Characterizing the Dynamics of
// Learning in Repeated Reference Games" (Cognitive Science 44, e12845); the "sequential" condition.
//
// Faithful design (from the paper + github.com/hawkrobe/tangrams):
// • 12 tangrams (the canonical set, shared with Clark & Wilkes-Gibbs 1986), in a 6×2 grid,
// independently scrambled per player each trial (can't point by position).
// • SINGLE target per trial; the matcher clicks the one tangram they think the director means.
// • 6 repetition blocks × 12 trials = 72 trials; each tangram is the target once per block.
// • Director/matcher roles fixed for the whole game.
// • "Cued" version: only the director sees which tangram is the target (reveal_target_to).
// • Free text chat, UNRESTRICTED both ways (both may send, no count/length limit) — the paper's
// exact channel, and what makes the reference-shortening effect possible.
// • Full immediate feedback to both: the director sees the matcher's click, the matcher sees the
// intended tangram.
// • The matcher cannot click until the director has sent a message that round
// (require_message_before_response), so a referring expression exists on every trial and the
// matcher can't blind-guess to rush through — verified in hawkrobe/tangrams game.client.js,
// where the click handler is gated behind `if (globalGame.messageSent)`.
//
// Every parameter above was verified against the paper + hawkrobe/tangrams code. The one departure
// is not a design choice: `round_timeout` (60s) has no counterpart in the original, which ran
// untimed. It exists so a disconnected or absent partner cannot hang the trial forever. Rounds it
// ends are recorded as `ended_by: "timeout"` with a null assignment — filter on that when
// analysing, and raise it if piloting shows real trials running long.
//
// Key replication DVs (logged per round): message_count / messages_sent (words-per-reference proxy),
// accuracy / correct, rt. Expected: descriptions shorten strongly across blocks; accuracy near
// ceiling by the final block.
// ===============================================================================================
const MIN_PLAYERS = 2;
// Faithful is 6. Lower this (e.g. 1–2) for a quick two-tab smoke test, then restore to 6.
const BLOCKS = 6;
const jsPsych = initJsPsych();
const localAdapter = new jsPsychAdapterMultiplayerLocal({ persistParticipant: true });
const SHAPES = Tangrams.STIMULI; // 12 canonical tangrams (ids A–L, image src)
// Seed the schedule from the SHARED SESSION ID, so each dyad gets its own trial order while the
// two partners still agree on it. A fixed seed would hand every dyad the identical order, which
// confounds item order with repetition block — the very axis this study measures.
//
// Read AFTER the adapter is constructed, deliberately: on the first tab there is no `?mp_session=`
// yet, and the LocalAdapter constructor is what generates one and writes it into the URL. The
// second tab arrives with it already in the pasted link, so both compute the same schedule.
// Swapping to the Firebase adapter: seed from whatever identifies the dyad there (e.g. the room
// id) — the requirement is only that both partners derive the same value.
const SEED = new URLSearchParams(location.search).get("mp_session") ?? "hawkins-sequential";
// 72 rounds: 6 blocks × 12, each tangram once per block. Deterministic given the seed, so both
// tabs agree without exchanging anything.
const SCHEDULE = Tangrams.sequentialSchedule(BLOCKS, SEED);
// The 12 tangrams are real PNGs, and `rt` is a reported DV — without this the first exposure to
// each image loads mid-trial. `stimuli` is an OBJECT parameter, so jsPsych's automatic media
// preloading cannot discover the `src` values; they have to be listed explicitly.
const preloadTrial = {
type: jsPsychPreload,
images: SHAPES.map((s) => s.src),
message: "<p>Loading…</p>",
};
let myName = "";
let myRole;
let partnerId;
const nameTrial = {
type: jsPsychSurveyText,
questions: [{ prompt: "Choose a display name:", required: true, name: "name" }],
on_finish: (data) => {
myName = (data.response.name || "").trim() || "Anonymous";
},
};
const lobbyTrial = {
type: jsPsychMultiplayerSync,
push_data: () => ({ name: myName, joinedAt: Date.now() }),
message: `<p>Waiting for ${MIN_PLAYERS} players to join…</p>
<p>Open this page in another tab (keep the <code>?mp_session=</code> in the URL) to add a player.</p>`,
wait_for: (group) =>
Object.values(group).filter((entry) => entry && entry.name).length >= MIN_PLAYERS,
};
// Fixed director/matcher for the whole game, assigned by deterministic consensus (shared seed from
// the sorted participant ids) — not "whoever joined first". Use "join_order" while testing if you
// want the first tab to always be the director.
const roleTrial = {
type: jsPsychMultiplayerRole,
roles: ["director", "matcher"],
strategy: "random",
overflow_role: "spectator",
ready: (group) =>
Object.keys(group).length >= MIN_PLAYERS &&
Object.values(group).every((entry) => entry && entry.joinedAt != null),
save_group: true,
message: "<p>Pairing you with your partner…</p>",
on_finish: () => {
myRole = jsPsychMultiplayerRole.getMyRole();
const byRole = jsPsychMultiplayerRole.participantsByRole();
partnerId = myRole === "director" ? byRole.matcher?.[0] : byRole.director?.[0];
},
};
const spectatorScreen = {
timeline: [
{
type: jsPsychHtmlKeyboardResponse,
stimulus: "<p>This game is already full. Thanks for your interest!</p>",
choices: "NO_KEYS",
trial_duration: 4000,
on_finish: () => jsPsych.multiplayer.disconnect(),
},
],
conditional_function: () => myRole === "spectator",
};
const gameRound = {
type: jsPsychMultiplayerReferenceGame,
stimuli: SHAPES,
columns: 6, // 6×2 grid, as in the original
role: () => jsPsychMultiplayerRole.getMyRole(),
partner_id: () => partnerId,
round: jsPsych.timelineVariable("round"),
targets: jsPsych.timelineVariable("targets"), // length 1 → single click (sequential)
scramble_mode: "independent",
reveal_target_to: "director", // "cued": only the director sees the target
// Unrestricted free chat, both directions — the paper's exact channel (these are the defaults,
// stated explicitly here because they are load-bearing for the effect).
chat_role: "both",
max_messages: null,
max_length: null,
// Exp. 2's click gate: no selection until the director has described the target this round.
// Guarantees a referring expression per trial and blocks blind-guessing.
require_message_before_response: true,
show_running_score: true,
round_timeout: 60000, // NOT in the original — bounds the round if the partner leaves (see header)
save_interaction_history: true,
prompt: (role) =>
role === "director"
? "<p><strong>You are the Director.</strong> Describe the highlighted tangram so your partner can pick it out.</p>"
: "<p><strong>You are the Matcher.</strong> Click the tangram your partner is describing.</p>",
};
const gameLoop = {
timeline: [gameRound],
timeline_variables: SCHEDULE,
conditional_function: () => myRole === "director" || myRole === "matcher",
};
const doneTrial = {
type: jsPsychHtmlKeyboardResponse,
stimulus: () => {
const completed = jsPsych.data
.get()
.filter({ trial_type: "multiplayer-reference-game", ended_by: "submit" });
const played = completed.count();
const correct = completed.filter({ correct: true }).count();
return `<h2>Game complete!</h2>
<p>Together you got <strong>${correct} of ${played}</strong> round${
played === 1 ? "" : "s"
} correct.</p>
<p>You may close this tab. <button id="dl">Download data (JSON)</button></p>`;
},
choices: "NO_KEYS",
on_load: () => {
document
.getElementById("dl")
?.addEventListener("click", () =>
jsPsych.data.get().localSave("json", "reference-game-hawkins-data.json")
);
},
};
jsPsych.multiplayer.connect(localAdapter).then(() => {
// Preload FIRST, before pairing: a participant who is still fetching images while their
// partner waits in the lobby wastes the partner's time.
jsPsych.run([
preloadTrial,
nameTrial,
lobbyTrial,
roleTrial,
spectatorScreen,
gameLoop,
doneTrial,
]);
});
</script>
</html>