jspsych-tangram 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/construct/index.browser.js +4805 -3935
- package/dist/construct/index.browser.js.map +1 -1
- package/dist/construct/index.browser.min.js +13 -13
- package/dist/construct/index.browser.min.js.map +1 -1
- package/dist/construct/index.cjs +289 -71
- package/dist/construct/index.cjs.map +1 -1
- package/dist/construct/index.d.ts +36 -0
- package/dist/construct/index.js +289 -71
- package/dist/construct/index.js.map +1 -1
- package/dist/index.cjs +399 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +84 -0
- package/dist/index.js +399 -100
- package/dist/index.js.map +1 -1
- package/dist/nback/index.browser.js +4629 -3939
- package/dist/nback/index.browser.js.map +1 -1
- package/dist/nback/index.browser.min.js +12 -12
- package/dist/nback/index.browser.min.js.map +1 -1
- package/dist/nback/index.cjs +102 -64
- package/dist/nback/index.cjs.map +1 -1
- package/dist/nback/index.d.ts +24 -0
- package/dist/nback/index.js +102 -64
- package/dist/nback/index.js.map +1 -1
- package/dist/prep/index.browser.js +4803 -3941
- package/dist/prep/index.browser.js.map +1 -1
- package/dist/prep/index.browser.min.js +13 -13
- package/dist/prep/index.browser.min.js.map +1 -1
- package/dist/prep/index.cjs +285 -75
- package/dist/prep/index.cjs.map +1 -1
- package/dist/prep/index.d.ts +24 -0
- package/dist/prep/index.js +285 -75
- package/dist/prep/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/components/board/BoardView.tsx +372 -124
- package/src/core/components/board/GameBoard.tsx +128 -91
- package/src/core/components/pieces/BlueprintRing.tsx +105 -47
- package/src/core/config/config.ts +25 -10
- package/src/plugins/tangram-construct/ConstructionApp.tsx +7 -1
- package/src/plugins/tangram-construct/index.ts +22 -1
- package/src/plugins/tangram-nback/NBackApp.tsx +87 -28
- package/src/plugins/tangram-nback/index.ts +14 -0
- package/src/plugins/tangram-prep/PrepApp.tsx +7 -1
- package/src/plugins/tangram-prep/index.ts +14 -0
- package/tangram-construct.min.js +13 -13
- package/tangram-nback.min.js +12 -12
- package/tangram-prep.min.js +13 -13
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GameBoard - Core reusable tangram game board component
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This component provides the complete tangram game functionality that can be
|
|
5
5
|
* shared between jsPsych plugins. It handles both construction and preparation
|
|
6
6
|
* game modes with full interaction support.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* ## Key Features
|
|
9
9
|
* - **Dependency Injection**: All game content provided via props (no hardcoded data)
|
|
10
10
|
* - **Multiple Input Modes**: Supports both click and drag interactions
|
|
11
11
|
* - **Layout Flexibility**: Circle and semicircle layout modes
|
|
12
12
|
* - **Event Tracking**: Comprehensive data collection for research
|
|
13
13
|
* - **State Management**: Uses BaseGameController for robust state handling
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
15
|
* ## Architecture
|
|
16
16
|
* - Uses modern React patterns with hooks
|
|
17
17
|
* - Integrates with BaseGameController for state management
|
|
18
18
|
* - Renders via Board component with computed layout
|
|
19
19
|
* - Handles completion events and data collection
|
|
20
|
-
*
|
|
20
|
+
*
|
|
21
21
|
* ## Usage in Plugins
|
|
22
22
|
* ```typescript
|
|
23
23
|
* // In jsPsych plugin trial method:
|
|
24
24
|
* const root = createRoot(display_element);
|
|
25
25
|
* root.render(React.createElement(GameBoard, {
|
|
26
26
|
* sectors: convertedSectors,
|
|
27
|
-
* quickstash: convertedMacros,
|
|
27
|
+
* quickstash: convertedMacros,
|
|
28
28
|
* primitives: PRIMITIVE_BLUEPRINTS,
|
|
29
29
|
* layout: "semicircle",
|
|
30
30
|
* target: "silhouette",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* onComplete: handleTrialComplete
|
|
33
33
|
* }));
|
|
34
34
|
* ```
|
|
35
|
-
*
|
|
35
|
+
*
|
|
36
36
|
* @see {@link BaseGameController} For state management details
|
|
37
37
|
* @see {@link Board} For rendering implementation
|
|
38
38
|
* @since Phase 3.3 - Extracted from monolithic Board component
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
|
|
42
42
|
import React from "react";
|
|
43
43
|
import { BaseGameController } from "@/core/engine/state/BaseGameController";
|
|
44
|
-
import type {
|
|
45
|
-
Sector,
|
|
46
|
-
Blueprint,
|
|
44
|
+
import type {
|
|
45
|
+
Sector,
|
|
46
|
+
Blueprint,
|
|
47
47
|
PrimitiveBlueprint,
|
|
48
48
|
LayoutMode,
|
|
49
49
|
PlacementTarget,
|
|
@@ -54,8 +54,8 @@ import { solveLogicalBox } from "@/core/domain/solve";
|
|
|
54
54
|
import type { Poly } from "@/core/domain/types";
|
|
55
55
|
import { CONFIG } from "@/core/config/config";
|
|
56
56
|
import BoardView, { type AnchorDots as DotsType } from "./BoardView";
|
|
57
|
-
import {
|
|
58
|
-
inferUnitFromPolys,
|
|
57
|
+
import {
|
|
58
|
+
inferUnitFromPolys,
|
|
59
59
|
placeSilhouetteGridAlignedAsPolys } from "@/core/engine/geometry";
|
|
60
60
|
import { anchorsSilhouetteComplete, anchorsWorkspaceComplete } from "@/core/engine/validation/complete";
|
|
61
61
|
import { scalePolys } from "@/core/components/board/utils";
|
|
@@ -67,109 +67,109 @@ import { InteractionTracker } from "@/core/io/InteractionTracker";
|
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Configuration interface for GameBoard component
|
|
70
|
-
*
|
|
70
|
+
*
|
|
71
71
|
* This interface defines all the required game content and behavior settings
|
|
72
|
-
* that must be provided to the GameBoard component. All properties use
|
|
72
|
+
* that must be provided to the GameBoard component. All properties use
|
|
73
73
|
* dependency injection - no defaults are provided.
|
|
74
74
|
*/
|
|
75
75
|
export interface GameBoardConfig {
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
77
|
* Array of sector definitions containing target silhouettes to construct
|
|
78
78
|
* Each sector represents one puzzle to solve
|
|
79
79
|
*/
|
|
80
80
|
sectors: Sector[];
|
|
81
|
-
|
|
82
|
-
/**
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
83
|
* Array of pre-made composite piece blueprints (macros)
|
|
84
84
|
* Usually created in prep trials and passed to construction trials
|
|
85
85
|
*/
|
|
86
86
|
quickstash: Blueprint[];
|
|
87
|
-
|
|
88
|
-
/**
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
89
|
* Array of primitive tangram piece definitions
|
|
90
90
|
* These are the basic 7 tangram shapes that never change
|
|
91
91
|
*/
|
|
92
92
|
primitives: PrimitiveBlueprint[];
|
|
93
|
-
|
|
94
|
-
/**
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
95
|
* Layout mode for the game board
|
|
96
96
|
* - "circle": Full circular layout with pieces arranged in complete circle
|
|
97
97
|
* - "semicircle": Half-circle layout with pieces in arc above workspace
|
|
98
98
|
*/
|
|
99
99
|
layout: LayoutMode;
|
|
100
|
-
|
|
101
|
-
/**
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
102
|
* Target placement mode for piece validation
|
|
103
103
|
* - "workspace": Pieces can be placed freely in workspace area
|
|
104
104
|
* - "silhouette": Pieces must be placed within target silhouette bounds
|
|
105
105
|
*/
|
|
106
106
|
target: PlacementTarget;
|
|
107
|
-
|
|
108
|
-
/**
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
109
|
* Input interaction mode
|
|
110
110
|
* - "click": Click to select pieces, click to place (better for touch)
|
|
111
111
|
* - "drag": Drag pieces directly with mouse/touch
|
|
112
112
|
*/
|
|
113
113
|
input: InputMode;
|
|
114
|
-
|
|
115
|
-
/**
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
116
|
* Time limit for the trial in milliseconds
|
|
117
117
|
* Set to 0 for no time limit
|
|
118
118
|
*/
|
|
119
119
|
timeLimitMs: number;
|
|
120
|
-
|
|
121
|
-
/**
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
122
|
* Maximum number of quickstash/macro slots to display
|
|
123
123
|
* Controls size of the macro selection ring
|
|
124
124
|
*/
|
|
125
125
|
maxQuickstashSlots: number;
|
|
126
|
-
|
|
127
|
-
/**
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
128
|
* Maximum number of primitive pieces allowed in a composite
|
|
129
129
|
* Used for validation when creating macros (prep mode only)
|
|
130
130
|
*/
|
|
131
131
|
maxCompositeSize?: number;
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
/**
|
|
134
134
|
* Game mode to determine behavior differences
|
|
135
135
|
* - "construction": Standard puzzle-solving mode with toggle
|
|
136
136
|
* - "prep": Macro creation mode with primitives-only center
|
|
137
137
|
*/
|
|
138
138
|
mode?: "construction" | "prep";
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
/**
|
|
141
141
|
* Minimum pieces required per macro (prep mode only)
|
|
142
142
|
* Used for submit button validation
|
|
143
143
|
*/
|
|
144
144
|
minPiecesPerMacro?: number;
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
/**
|
|
147
147
|
* Whether all slots must be filled to complete trial (prep mode only)
|
|
148
148
|
* Used for submit button validation
|
|
149
149
|
*/
|
|
150
150
|
requireAllSlots?: boolean;
|
|
151
|
-
|
|
151
|
+
|
|
152
152
|
/**
|
|
153
153
|
* Whether to show tangram target shapes decomposed into individual primitives with borders
|
|
154
154
|
* Independent from showBorders/hideTouchingBorders (which control spawned pieces)
|
|
155
155
|
*/
|
|
156
156
|
showTangramDecomposition?: boolean;
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
/**
|
|
159
159
|
* HTML content to display above the gameboard as instructions
|
|
160
160
|
* Allows rich formatting via HTML
|
|
161
161
|
*/
|
|
162
162
|
instructions?: string;
|
|
163
|
-
|
|
163
|
+
|
|
164
164
|
/**
|
|
165
165
|
* Cleaned jsPsych plugin parameters (excluding callbacks)
|
|
166
166
|
* Saved to trial data for analysis
|
|
167
167
|
*/
|
|
168
168
|
trialParams?: any;
|
|
169
|
-
|
|
169
|
+
|
|
170
170
|
/** Optional CSS width for the game board SVG */
|
|
171
171
|
width?: number;
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
/** Optional CSS height for the game board SVG */
|
|
174
174
|
height?: number;
|
|
175
175
|
}
|
|
@@ -218,29 +218,47 @@ export interface GameBoardProps extends GameBoardConfig {
|
|
|
218
218
|
* Useful for spawning initial pieces or setting up state
|
|
219
219
|
*/
|
|
220
220
|
onControllerReady?: (controller: BaseGameController, layout?: any, force?: () => void) => void;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Whether to use distinct colors for each primitive shape type in blueprints
|
|
224
|
+
* When true, each primitive type (square, triangle, etc.) gets its own color in dock area
|
|
225
|
+
*/
|
|
226
|
+
usePrimitiveColorsBlueprints?: boolean;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Whether to use distinct colors for each primitive shape type in target tangrams
|
|
230
|
+
* When true, each primitive type gets its own color in target silhouettes
|
|
231
|
+
*/
|
|
232
|
+
usePrimitiveColorsTargets?: boolean;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Array of 5 integers mapping primitives to colors from CONFIG.color.primitiveColors
|
|
236
|
+
* Maps [square, smalltriangle, parallelogram, medtriangle, largetriangle] to color indices
|
|
237
|
+
*/
|
|
238
|
+
primitiveColorIndices?: number[];
|
|
221
239
|
}
|
|
222
240
|
|
|
223
241
|
/**
|
|
224
242
|
* Core GameBoard component that encapsulates all tangram game functionality
|
|
225
|
-
*
|
|
243
|
+
*
|
|
226
244
|
* This is the main reusable component that provides complete tangram game
|
|
227
245
|
* functionality for jsPsych plugins. It handles all aspects of the game
|
|
228
246
|
* including piece interaction, collision detection, completion validation,
|
|
229
247
|
* and data collection.
|
|
230
|
-
*
|
|
248
|
+
*
|
|
231
249
|
* ## Key Responsibilities
|
|
232
250
|
* - Creates and manages BaseGameController for state management
|
|
233
|
-
* - Computes layout geometry for board rendering
|
|
251
|
+
* - Computes layout geometry for board rendering
|
|
234
252
|
* - Handles event callbacks for plugin integration
|
|
235
253
|
* - Manages CSS sizing for responsive display
|
|
236
|
-
*
|
|
254
|
+
*
|
|
237
255
|
* ## State Management
|
|
238
256
|
* Uses BaseGameController internally which provides:
|
|
239
257
|
* - Piece placement and validation
|
|
240
258
|
* - Sector completion tracking
|
|
241
259
|
* - Event logging for data collection
|
|
242
260
|
* - Collision detection and snapping
|
|
243
|
-
*
|
|
261
|
+
*
|
|
244
262
|
* @param props - Configuration and event callbacks for the game
|
|
245
263
|
* @returns React component rendering the complete tangram game interface
|
|
246
264
|
*/
|
|
@@ -266,12 +284,15 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
266
284
|
onPieceRemove,
|
|
267
285
|
onInteraction,
|
|
268
286
|
onTrialEnd,
|
|
269
|
-
onControllerReady
|
|
287
|
+
onControllerReady,
|
|
288
|
+
usePrimitiveColorsBlueprints,
|
|
289
|
+
usePrimitiveColorsTargets,
|
|
290
|
+
primitiveColorIndices,
|
|
270
291
|
} = props;
|
|
271
|
-
|
|
292
|
+
|
|
272
293
|
// Timer state for countdown
|
|
273
294
|
const [timeRemaining, setTimeRemaining] = React.useState(timeLimitMs > 0 ? Math.floor(timeLimitMs / 1000) : 0);
|
|
274
|
-
|
|
295
|
+
|
|
275
296
|
// Initialize game controller with injected data
|
|
276
297
|
const controller = React.useMemo(() => {
|
|
277
298
|
const gameConfig = {
|
|
@@ -286,11 +307,11 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
286
307
|
...(props.minPiecesPerMacro !== undefined && { minPiecesPerMacro: props.minPiecesPerMacro }),
|
|
287
308
|
...(props.requireAllSlots !== undefined && { requireAllSlots: props.requireAllSlots })
|
|
288
309
|
};
|
|
289
|
-
|
|
310
|
+
|
|
290
311
|
return new BaseGameController(
|
|
291
312
|
sectors,
|
|
292
313
|
quickstash,
|
|
293
|
-
primitives,
|
|
314
|
+
primitives,
|
|
294
315
|
gameConfig
|
|
295
316
|
);
|
|
296
317
|
}, [sectors, quickstash, primitives, layoutMode, target, input, timeLimitMs, maxQuickstashSlots, maxCompositeSize, mode, props.minPiecesPerMacro, props.requireAllSlots]);
|
|
@@ -322,12 +343,12 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
322
343
|
};
|
|
323
344
|
}, [tracker]);
|
|
324
345
|
|
|
325
|
-
// Compute layout geometry
|
|
346
|
+
// Compute layout geometry
|
|
326
347
|
const { layout, viewBox } = React.useMemo(() => {
|
|
327
348
|
const nSectors = sectors.length;
|
|
328
349
|
const sectorIds = sectors.map(s => s.id);
|
|
329
350
|
const masksPerSector: Poly[][] = sectors.map(s => s.silhouette.mask ?? []);
|
|
330
|
-
|
|
351
|
+
|
|
331
352
|
const logicalBox = solveLogicalBox({
|
|
332
353
|
n: nSectors,
|
|
333
354
|
layoutMode,
|
|
@@ -337,7 +358,7 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
337
358
|
layoutPadPx: CONFIG.layout.paddingPx,
|
|
338
359
|
masks: masksPerSector,
|
|
339
360
|
});
|
|
340
|
-
|
|
361
|
+
|
|
341
362
|
const layout = computeCircleLayout(
|
|
342
363
|
{ w: logicalBox.LOGICAL_W, h: logicalBox.LOGICAL_H },
|
|
343
364
|
nSectors,
|
|
@@ -350,7 +371,7 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
350
371
|
masks: masksPerSector,
|
|
351
372
|
}
|
|
352
373
|
);
|
|
353
|
-
|
|
374
|
+
|
|
354
375
|
return {
|
|
355
376
|
layout,
|
|
356
377
|
viewBox: { w: logicalBox.LOGICAL_W, h: logicalBox.LOGICAL_H }
|
|
@@ -366,10 +387,10 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
366
387
|
onControllerReady(controller, layout, force);
|
|
367
388
|
}
|
|
368
389
|
}, [controller, layout, onControllerReady, force]);
|
|
369
|
-
|
|
390
|
+
|
|
370
391
|
// Game completion tracking
|
|
371
392
|
const [gameCompleted, setGameCompleted] = React.useState(false);
|
|
372
|
-
|
|
393
|
+
|
|
373
394
|
// Watch for game completion
|
|
374
395
|
React.useEffect(() => {
|
|
375
396
|
const checkGameCompletion = () => {
|
|
@@ -393,21 +414,21 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
393
414
|
// Check completion whenever controller state updates
|
|
394
415
|
checkGameCompletion();
|
|
395
416
|
}, [controller.updateCount, sectors, gameCompleted, controller, tracker]);
|
|
396
|
-
|
|
417
|
+
|
|
397
418
|
// Sector completion callback
|
|
398
419
|
const handleSectorComplete = React.useCallback((sectorId: string) => {
|
|
399
420
|
if (onSectorComplete) {
|
|
400
421
|
onSectorComplete(sectorId, controller.snapshot());
|
|
401
422
|
}
|
|
402
423
|
}, [onSectorComplete, controller]);
|
|
403
|
-
|
|
424
|
+
|
|
404
425
|
// Event callbacks for plugin integration
|
|
405
426
|
const eventCallbacks = React.useMemo(() => ({
|
|
406
427
|
onSectorComplete: handleSectorComplete,
|
|
407
428
|
onPiecePlace: onPiecePlace || (() => {}),
|
|
408
429
|
onPieceRemove: onPieceRemove || (() => {})
|
|
409
430
|
}), [handleSectorComplete, onPiecePlace, onPieceRemove]);
|
|
410
|
-
|
|
431
|
+
|
|
411
432
|
// Calculate sizing based on layout mode
|
|
412
433
|
const getGameboardStyle = () => {
|
|
413
434
|
const baseStyle = {
|
|
@@ -560,14 +581,14 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
560
581
|
tracker // Pass tracker for data collection
|
|
561
582
|
);
|
|
562
583
|
|
|
563
|
-
const {
|
|
564
|
-
draggingId,
|
|
565
|
-
dragInvalid,
|
|
566
|
-
svgRef,
|
|
567
|
-
onPiecePointerDown,
|
|
568
|
-
onBlueprintPointerDown,
|
|
569
|
-
onPointerMove,
|
|
570
|
-
onPointerUp,
|
|
584
|
+
const {
|
|
585
|
+
draggingId,
|
|
586
|
+
dragInvalid,
|
|
587
|
+
svgRef,
|
|
588
|
+
onPiecePointerDown,
|
|
589
|
+
onBlueprintPointerDown,
|
|
590
|
+
onPointerMove,
|
|
591
|
+
onPointerUp,
|
|
571
592
|
setPieceRef,
|
|
572
593
|
setDraggingId,
|
|
573
594
|
lockedPieceId,
|
|
@@ -640,7 +661,7 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
640
661
|
// Timer effect
|
|
641
662
|
React.useEffect(() => {
|
|
642
663
|
if (timeLimitMs === 0) return;
|
|
643
|
-
|
|
664
|
+
|
|
644
665
|
const interval = setInterval(() => {
|
|
645
666
|
setTimeRemaining(prev => {
|
|
646
667
|
if (prev <= 1) {
|
|
@@ -650,7 +671,7 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
650
671
|
return prev - 1;
|
|
651
672
|
});
|
|
652
673
|
}, 1000);
|
|
653
|
-
|
|
674
|
+
|
|
654
675
|
return () => clearInterval(interval);
|
|
655
676
|
}, [timeLimitMs]);
|
|
656
677
|
|
|
@@ -669,27 +690,38 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
669
690
|
// minHeight: '100vh'
|
|
670
691
|
};
|
|
671
692
|
|
|
672
|
-
// Header style
|
|
693
|
+
// Header style (constrained to gameboard width)
|
|
673
694
|
const headerStyle: React.CSSProperties = {
|
|
674
695
|
display: 'flex',
|
|
675
696
|
flexDirection: 'row',
|
|
676
|
-
justifyContent: '
|
|
697
|
+
justifyContent: 'center',
|
|
677
698
|
alignItems: 'center',
|
|
678
699
|
padding: '20px',
|
|
679
|
-
background:
|
|
700
|
+
background: CONFIG.color.background,
|
|
680
701
|
flex: '0 0 auto'
|
|
681
702
|
};
|
|
682
703
|
|
|
683
|
-
//
|
|
704
|
+
// Header content wrapper (matches gameboard width)
|
|
705
|
+
const headerContentStyle: React.CSSProperties = {
|
|
706
|
+
position: 'relative',
|
|
707
|
+
width: `${svgDimensions.width}px`,
|
|
708
|
+
maxWidth: '100%',
|
|
709
|
+
display: 'flex',
|
|
710
|
+
justifyContent: 'center',
|
|
711
|
+
alignItems: 'center'
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// Instructions style (always centered)
|
|
684
715
|
const instructionsStyle: React.CSSProperties = {
|
|
685
|
-
flexGrow: 1,
|
|
686
716
|
fontSize: '20px',
|
|
687
717
|
lineHeight: 1.5,
|
|
688
|
-
|
|
718
|
+
textAlign: 'center'
|
|
689
719
|
};
|
|
690
720
|
|
|
691
|
-
// Timer style
|
|
721
|
+
// Timer style (positioned absolutely to not affect centering)
|
|
692
722
|
const timerStyle: React.CSSProperties = {
|
|
723
|
+
position: 'absolute',
|
|
724
|
+
right: 0,
|
|
693
725
|
fontSize: '24px',
|
|
694
726
|
fontWeight: 'bold',
|
|
695
727
|
fontFamily: 'monospace',
|
|
@@ -711,18 +743,20 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
711
743
|
<div className="tangram-trial-container" style={containerStyle}>
|
|
712
744
|
{(instructions || timeLimitMs > 0) && (
|
|
713
745
|
<div className="tangram-header" style={headerStyle}>
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
{
|
|
724
|
-
|
|
725
|
-
|
|
746
|
+
<div className="tangram-header-content" style={headerContentStyle}>
|
|
747
|
+
{instructions && (
|
|
748
|
+
<div
|
|
749
|
+
className="tangram-instructions"
|
|
750
|
+
style={instructionsStyle}
|
|
751
|
+
dangerouslySetInnerHTML={{ __html: instructions }}
|
|
752
|
+
/>
|
|
753
|
+
)}
|
|
754
|
+
{timeLimitMs > 0 && (
|
|
755
|
+
<div className="tangram-timer" style={timerStyle}>
|
|
756
|
+
{formatTime(timeRemaining)}
|
|
757
|
+
</div>
|
|
758
|
+
)}
|
|
759
|
+
</div>
|
|
726
760
|
</div>
|
|
727
761
|
)}
|
|
728
762
|
<div className="tangram-gameboard-wrapper" style={gameboardWrapperStyle}>
|
|
@@ -753,6 +787,9 @@ export default function GameBoard(props: GameBoardProps) {
|
|
|
753
787
|
onCenterBadgePointerDown={onCenterBadgePointerDown}
|
|
754
788
|
showTangramDecomposition={showTangramDecomposition ?? false}
|
|
755
789
|
scaleS={scaleS}
|
|
790
|
+
usePrimitiveColorsBlueprints={usePrimitiveColorsBlueprints ?? false}
|
|
791
|
+
usePrimitiveColorsTargets={usePrimitiveColorsTargets ?? false}
|
|
792
|
+
primitiveColorIndices={primitiveColorIndices ?? [0, 1, 2, 3, 4]}
|
|
756
793
|
{...eventCallbacks}
|
|
757
794
|
/>
|
|
758
795
|
</div>
|
|
@@ -779,7 +816,7 @@ export function useGameBoard(config: GameBoardConfig) {
|
|
|
779
816
|
...(config.minPiecesPerMacro !== undefined && { minPiecesPerMacro: config.minPiecesPerMacro }),
|
|
780
817
|
...(config.requireAllSlots !== undefined && { requireAllSlots: config.requireAllSlots })
|
|
781
818
|
};
|
|
782
|
-
|
|
819
|
+
|
|
783
820
|
return new BaseGameController(
|
|
784
821
|
config.sectors,
|
|
785
822
|
config.quickstash,
|
|
@@ -787,12 +824,12 @@ export function useGameBoard(config: GameBoardConfig) {
|
|
|
787
824
|
gameConfig
|
|
788
825
|
);
|
|
789
826
|
}, [config]);
|
|
790
|
-
|
|
791
|
-
const snapshot = React.useMemo(() =>
|
|
792
|
-
controller.snapshot(),
|
|
827
|
+
|
|
828
|
+
const snapshot = React.useMemo(() =>
|
|
829
|
+
controller.snapshot(),
|
|
793
830
|
[controller.updateCount]
|
|
794
831
|
);
|
|
795
|
-
|
|
832
|
+
|
|
796
833
|
return {
|
|
797
834
|
controller,
|
|
798
835
|
snapshot,
|