@almadar/ui 5.46.0 → 5.48.0
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/avl/index.cjs +338 -316
- package/dist/avl/index.js +338 -316
- package/dist/components/game/atoms/ComboCounter.d.ts +4 -1
- package/dist/components/game/atoms/ResourceCounter.d.ts +4 -1
- package/dist/components/game/atoms/StateIndicator.d.ts +4 -7
- package/dist/components/game/atoms/StatusEffect.d.ts +4 -1
- package/dist/components/game/atoms/WaypointMarker.d.ts +4 -1
- package/dist/components/game/molecules/three/index.cjs +89 -80
- package/dist/components/game/molecules/three/index.css +0 -5
- package/dist/components/game/molecules/three/index.js +89 -80
- package/dist/components/game/organisms/CastleBoard.d.ts +10 -1
- package/dist/components/game/organisms/puzzles/event-handler/EventHandlerBoard.d.ts +9 -1
- package/dist/components/game/organisms/puzzles/state-architect/StateArchitectBoard.d.ts +14 -1
- package/dist/components/index.cjs +338 -316
- package/dist/components/index.js +338 -316
- package/dist/providers/index.cjs +338 -316
- package/dist/providers/index.js +338 -316
- package/dist/runtime/index.cjs +338 -316
- package/dist/runtime/index.js +338 -316
- package/package.json +1 -1
- package/tailwind-preset.cjs +1 -0
package/dist/avl/index.js
CHANGED
|
@@ -11017,6 +11017,7 @@ var init_Sprite = __esm({
|
|
|
11017
11017
|
}
|
|
11018
11018
|
});
|
|
11019
11019
|
function StateIndicator({
|
|
11020
|
+
assetUrl = DEFAULT_ASSET_URL,
|
|
11020
11021
|
state = "idle",
|
|
11021
11022
|
label,
|
|
11022
11023
|
size = "md",
|
|
@@ -11039,18 +11040,29 @@ function StateIndicator({
|
|
|
11039
11040
|
className
|
|
11040
11041
|
),
|
|
11041
11042
|
children: [
|
|
11042
|
-
/* @__PURE__ */ jsx(Box, { as: "span", children:
|
|
11043
|
+
/* @__PURE__ */ jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsx(
|
|
11044
|
+
"img",
|
|
11045
|
+
{
|
|
11046
|
+
src: assetUrl,
|
|
11047
|
+
alt: displayLabel,
|
|
11048
|
+
width: 16,
|
|
11049
|
+
height: 16,
|
|
11050
|
+
style: { imageRendering: "pixelated", objectFit: "contain" },
|
|
11051
|
+
className: "flex-shrink-0"
|
|
11052
|
+
}
|
|
11053
|
+
) : typeof config.icon === "string" ? /^[a-zA-Z0-9-]+$/.test(config.icon) ? /* @__PURE__ */ jsx(Icon, { name: config.icon }) : config.icon : /* @__PURE__ */ jsx(Icon, { icon: config.icon }) }),
|
|
11043
11054
|
/* @__PURE__ */ jsx(Box, { as: "span", children: displayLabel })
|
|
11044
11055
|
]
|
|
11045
11056
|
}
|
|
11046
11057
|
);
|
|
11047
11058
|
}
|
|
11048
|
-
var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
|
|
11059
|
+
var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
|
|
11049
11060
|
var init_StateIndicator = __esm({
|
|
11050
11061
|
"components/game/atoms/StateIndicator.tsx"() {
|
|
11051
11062
|
init_Box();
|
|
11052
11063
|
init_Icon();
|
|
11053
11064
|
init_cn();
|
|
11065
|
+
DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
|
|
11054
11066
|
DEFAULT_STATE_STYLES = {
|
|
11055
11067
|
idle: { icon: "\u23F8", bgClass: "bg-muted" },
|
|
11056
11068
|
active: { icon: "\u25B6", bgClass: "bg-success" },
|
|
@@ -11125,6 +11137,7 @@ var init_TimerDisplay = __esm({
|
|
|
11125
11137
|
}
|
|
11126
11138
|
});
|
|
11127
11139
|
function ResourceCounter({
|
|
11140
|
+
assetUrl = DEFAULT_ASSET_URL2,
|
|
11128
11141
|
icon,
|
|
11129
11142
|
label = "Gold",
|
|
11130
11143
|
value = 250,
|
|
@@ -11144,7 +11157,17 @@ function ResourceCounter({
|
|
|
11144
11157
|
className
|
|
11145
11158
|
),
|
|
11146
11159
|
children: [
|
|
11147
|
-
|
|
11160
|
+
assetUrl ? /* @__PURE__ */ jsx(
|
|
11161
|
+
"img",
|
|
11162
|
+
{
|
|
11163
|
+
src: assetUrl,
|
|
11164
|
+
alt: label,
|
|
11165
|
+
width: sizes.img,
|
|
11166
|
+
height: sizes.img,
|
|
11167
|
+
style: { imageRendering: "pixelated", objectFit: "contain" },
|
|
11168
|
+
className: "flex-shrink-0"
|
|
11169
|
+
}
|
|
11170
|
+
) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
|
|
11148
11171
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
|
|
11149
11172
|
/* @__PURE__ */ jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
|
|
11150
11173
|
value,
|
|
@@ -11157,7 +11180,7 @@ function ResourceCounter({
|
|
|
11157
11180
|
}
|
|
11158
11181
|
);
|
|
11159
11182
|
}
|
|
11160
|
-
var colorTokenClasses2, sizeMap5;
|
|
11183
|
+
var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
|
|
11161
11184
|
var init_ResourceCounter = __esm({
|
|
11162
11185
|
"components/game/atoms/ResourceCounter.tsx"() {
|
|
11163
11186
|
init_cn();
|
|
@@ -11170,16 +11193,17 @@ var init_ResourceCounter = __esm({
|
|
|
11170
11193
|
error: "text-error",
|
|
11171
11194
|
muted: "text-muted-foreground"
|
|
11172
11195
|
};
|
|
11196
|
+
DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
|
|
11173
11197
|
sizeMap5 = {
|
|
11174
|
-
sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
|
|
11175
|
-
md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
|
|
11176
|
-
lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
|
|
11198
|
+
sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
|
|
11199
|
+
md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
|
|
11200
|
+
lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
|
|
11177
11201
|
};
|
|
11178
11202
|
ResourceCounter.displayName = "ResourceCounter";
|
|
11179
11203
|
}
|
|
11180
11204
|
});
|
|
11181
11205
|
function ItemSlot({
|
|
11182
|
-
assetUrl =
|
|
11206
|
+
assetUrl = DEFAULT_ASSET_URL3,
|
|
11183
11207
|
icon = "sword",
|
|
11184
11208
|
label = "Iron Sword",
|
|
11185
11209
|
quantity,
|
|
@@ -11238,7 +11262,7 @@ function ItemSlot({
|
|
|
11238
11262
|
}
|
|
11239
11263
|
);
|
|
11240
11264
|
}
|
|
11241
|
-
var sizeMap6, rarityBorderMap, rarityGlowMap,
|
|
11265
|
+
var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
|
|
11242
11266
|
var init_ItemSlot = __esm({
|
|
11243
11267
|
"components/game/atoms/ItemSlot.tsx"() {
|
|
11244
11268
|
"use client";
|
|
@@ -11263,7 +11287,7 @@ var init_ItemSlot = __esm({
|
|
|
11263
11287
|
epic: "shadow-lg",
|
|
11264
11288
|
legendary: "shadow-lg"
|
|
11265
11289
|
};
|
|
11266
|
-
|
|
11290
|
+
DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
|
|
11267
11291
|
assetSizeMap = {
|
|
11268
11292
|
sm: 28,
|
|
11269
11293
|
md: 40,
|
|
@@ -11336,6 +11360,7 @@ function getComboScale(combo) {
|
|
|
11336
11360
|
return "";
|
|
11337
11361
|
}
|
|
11338
11362
|
function ComboCounter({
|
|
11363
|
+
assetUrl = DEFAULT_ASSET_URL4,
|
|
11339
11364
|
combo = 5,
|
|
11340
11365
|
multiplier,
|
|
11341
11366
|
streak,
|
|
@@ -11355,6 +11380,17 @@ function ComboCounter({
|
|
|
11355
11380
|
className
|
|
11356
11381
|
),
|
|
11357
11382
|
children: [
|
|
11383
|
+
assetUrl && /* @__PURE__ */ jsx(
|
|
11384
|
+
"img",
|
|
11385
|
+
{
|
|
11386
|
+
src: assetUrl,
|
|
11387
|
+
alt: "combo",
|
|
11388
|
+
width: 24,
|
|
11389
|
+
height: 24,
|
|
11390
|
+
style: { imageRendering: "pixelated", objectFit: "contain" },
|
|
11391
|
+
className: "flex-shrink-0 mb-0.5"
|
|
11392
|
+
}
|
|
11393
|
+
),
|
|
11358
11394
|
/* @__PURE__ */ jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
|
|
11359
11395
|
/* @__PURE__ */ jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
|
|
11360
11396
|
multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
|
|
@@ -11369,10 +11405,11 @@ function ComboCounter({
|
|
|
11369
11405
|
}
|
|
11370
11406
|
);
|
|
11371
11407
|
}
|
|
11372
|
-
var sizeMap8;
|
|
11408
|
+
var DEFAULT_ASSET_URL4, sizeMap8;
|
|
11373
11409
|
var init_ComboCounter = __esm({
|
|
11374
11410
|
"components/game/atoms/ComboCounter.tsx"() {
|
|
11375
11411
|
init_cn();
|
|
11412
|
+
DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
|
|
11376
11413
|
sizeMap8 = {
|
|
11377
11414
|
sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
|
|
11378
11415
|
md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
|
|
@@ -11466,6 +11503,7 @@ var init_XPBar = __esm({
|
|
|
11466
11503
|
}
|
|
11467
11504
|
});
|
|
11468
11505
|
function WaypointMarker({
|
|
11506
|
+
assetUrl = DEFAULT_ASSET_URL5,
|
|
11469
11507
|
label,
|
|
11470
11508
|
icon,
|
|
11471
11509
|
active = true,
|
|
@@ -11504,7 +11542,17 @@ function WaypointMarker({
|
|
|
11504
11542
|
active && !completed && "bg-info text-foreground",
|
|
11505
11543
|
!active && !completed && "bg-muted"
|
|
11506
11544
|
),
|
|
11507
|
-
children: completed ? checkIcon :
|
|
11545
|
+
children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsx(
|
|
11546
|
+
"img",
|
|
11547
|
+
{
|
|
11548
|
+
src: assetUrl,
|
|
11549
|
+
alt: label,
|
|
11550
|
+
width: sizes.img,
|
|
11551
|
+
height: sizes.img,
|
|
11552
|
+
style: { imageRendering: "pixelated", objectFit: "contain" },
|
|
11553
|
+
className: "flex-shrink-0"
|
|
11554
|
+
}
|
|
11555
|
+
) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) : null
|
|
11508
11556
|
}
|
|
11509
11557
|
)
|
|
11510
11558
|
] }),
|
|
@@ -11521,15 +11569,16 @@ function WaypointMarker({
|
|
|
11521
11569
|
)
|
|
11522
11570
|
] });
|
|
11523
11571
|
}
|
|
11524
|
-
var sizeMap10, checkIcon;
|
|
11572
|
+
var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
|
|
11525
11573
|
var init_WaypointMarker = __esm({
|
|
11526
11574
|
"components/game/atoms/WaypointMarker.tsx"() {
|
|
11527
11575
|
init_cn();
|
|
11528
11576
|
init_Icon();
|
|
11577
|
+
DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
|
|
11529
11578
|
sizeMap10 = {
|
|
11530
|
-
sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
|
|
11531
|
-
md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
|
|
11532
|
-
lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
|
|
11579
|
+
sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
|
|
11580
|
+
md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
|
|
11581
|
+
lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
|
|
11533
11582
|
};
|
|
11534
11583
|
checkIcon = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 3, className: "w-3/5 h-3/5", children: /* @__PURE__ */ jsx("path", { d: "M5 13l4 4L19 7", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
11535
11584
|
WaypointMarker.displayName = "WaypointMarker";
|
|
@@ -11542,6 +11591,7 @@ function formatDuration(seconds) {
|
|
|
11542
11591
|
return `${Math.round(seconds)}s`;
|
|
11543
11592
|
}
|
|
11544
11593
|
function StatusEffect({
|
|
11594
|
+
assetUrl = DEFAULT_ASSET_URL6,
|
|
11545
11595
|
icon = "shield",
|
|
11546
11596
|
label = "Shield",
|
|
11547
11597
|
duration = 30,
|
|
@@ -11562,7 +11612,17 @@ function StatusEffect({
|
|
|
11562
11612
|
),
|
|
11563
11613
|
title: label,
|
|
11564
11614
|
children: [
|
|
11565
|
-
/* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children:
|
|
11615
|
+
/* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsx(
|
|
11616
|
+
"img",
|
|
11617
|
+
{
|
|
11618
|
+
src: assetUrl,
|
|
11619
|
+
alt: label,
|
|
11620
|
+
width: sizes.img,
|
|
11621
|
+
height: sizes.img,
|
|
11622
|
+
style: { imageRendering: "pixelated", objectFit: "contain" },
|
|
11623
|
+
className: "flex-shrink-0"
|
|
11624
|
+
}
|
|
11625
|
+
) : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
|
|
11566
11626
|
duration !== void 0 && /* @__PURE__ */ jsx(
|
|
11567
11627
|
"span",
|
|
11568
11628
|
{
|
|
@@ -11589,15 +11649,16 @@ function StatusEffect({
|
|
|
11589
11649
|
label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
|
|
11590
11650
|
] });
|
|
11591
11651
|
}
|
|
11592
|
-
var sizeMap11, variantStyles7;
|
|
11652
|
+
var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
|
|
11593
11653
|
var init_StatusEffect = __esm({
|
|
11594
11654
|
"components/game/atoms/StatusEffect.tsx"() {
|
|
11595
11655
|
init_cn();
|
|
11596
11656
|
init_Icon();
|
|
11657
|
+
DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
|
|
11597
11658
|
sizeMap11 = {
|
|
11598
|
-
sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
|
|
11599
|
-
md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
|
|
11600
|
-
lg: { container: "w-12 h-12", icon: "text-lg", badge: "text-sm -top-1.5 -right-1.5 w-6 h-6", timer: "text-xs" }
|
|
11659
|
+
sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]", img: 20 },
|
|
11660
|
+
md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs", img: 28 },
|
|
11661
|
+
lg: { container: "w-12 h-12", icon: "text-lg", badge: "text-sm -top-1.5 -right-1.5 w-6 h-6", timer: "text-xs", img: 36 }
|
|
11601
11662
|
};
|
|
11602
11663
|
variantStyles7 = {
|
|
11603
11664
|
buff: "border-success bg-success/20",
|
|
@@ -15166,15 +15227,15 @@ function BattleBoard({
|
|
|
15166
15227
|
const board = boardEntity(entity) ?? {};
|
|
15167
15228
|
const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
|
|
15168
15229
|
const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
|
|
15169
|
-
const boardWidth = num(board.boardWidth, 8);
|
|
15170
|
-
const boardHeight = num(board.boardHeight, 6);
|
|
15230
|
+
const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
|
|
15231
|
+
const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
|
|
15171
15232
|
const assetManifest = propAssetManifest ?? board.assetManifest;
|
|
15172
15233
|
const backgroundImage = board.backgroundImage;
|
|
15173
15234
|
const units = rows(board.units);
|
|
15174
15235
|
const selectedUnitId = board.selectedUnitId ?? null;
|
|
15175
15236
|
const currentPhase = str(board.phase) || "observation";
|
|
15176
15237
|
const currentTurn = num(board.turn, 1);
|
|
15177
|
-
const gameResult = board.
|
|
15238
|
+
const gameResult = board.result ?? null;
|
|
15178
15239
|
const eventBus = useEventBus();
|
|
15179
15240
|
const { t } = useTranslate();
|
|
15180
15241
|
const [hoveredTile, setHoveredTile] = useState(null);
|
|
@@ -15195,7 +15256,7 @@ function BattleBoard({
|
|
|
15195
15256
|
const validMoves = useMemo(() => {
|
|
15196
15257
|
if (!selectedUnit || currentPhase !== "movement") return [];
|
|
15197
15258
|
const moves = [];
|
|
15198
|
-
const range = num(
|
|
15259
|
+
const range = num(board.movementRange, 2);
|
|
15199
15260
|
const origin = unitPosition(selectedUnit);
|
|
15200
15261
|
for (let dy = -range; dy <= range; dy++) {
|
|
15201
15262
|
for (let dx = -range; dx <= range; dx++) {
|
|
@@ -22651,15 +22712,24 @@ function CastleBoard({
|
|
|
22651
22712
|
featureClickEvent,
|
|
22652
22713
|
unitClickEvent,
|
|
22653
22714
|
tileClickEvent,
|
|
22715
|
+
playAgainEvent,
|
|
22654
22716
|
className
|
|
22655
22717
|
}) {
|
|
22656
22718
|
const eventBus = useEventBus();
|
|
22719
|
+
const { t } = useTranslate();
|
|
22657
22720
|
const resolved = boardEntity(entity);
|
|
22658
22721
|
const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
|
|
22659
22722
|
const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
|
|
22660
22723
|
const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
|
|
22661
22724
|
const assetManifest = propAssetManifest ?? resolved?.assetManifest;
|
|
22662
22725
|
const backgroundImage = resolved?.backgroundImage;
|
|
22726
|
+
const gold = num(resolved?.gold);
|
|
22727
|
+
const health = num(resolved?.health);
|
|
22728
|
+
const maxHealth = num(resolved?.maxHealth);
|
|
22729
|
+
const wave = num(resolved?.wave);
|
|
22730
|
+
const tickCount = num(resolved?.tickCount);
|
|
22731
|
+
const buildings = rows(resolved?.buildings);
|
|
22732
|
+
const result = str(resolved?.result) || "none";
|
|
22663
22733
|
const [hoveredTile, setHoveredTile] = useState(null);
|
|
22664
22734
|
const [selectedFeature, setSelectedFeature] = useState(null);
|
|
22665
22735
|
const hoveredFeature = useMemo(() => {
|
|
@@ -22672,7 +22742,7 @@ function CastleBoard({
|
|
|
22672
22742
|
(u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
|
|
22673
22743
|
) ?? null;
|
|
22674
22744
|
}, [hoveredTile, units]);
|
|
22675
|
-
const maxY = Math.max(...tiles.map((
|
|
22745
|
+
const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
|
|
22676
22746
|
const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
|
|
22677
22747
|
const tileToScreen = useCallback(
|
|
22678
22748
|
(tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
|
|
@@ -22707,6 +22777,11 @@ function CastleBoard({
|
|
|
22707
22777
|
}
|
|
22708
22778
|
}, [units, onUnitClick, unitClickEvent, eventBus]);
|
|
22709
22779
|
const clearSelection = useCallback(() => setSelectedFeature(null), []);
|
|
22780
|
+
const handlePlayAgain = useCallback(() => {
|
|
22781
|
+
if (playAgainEvent) {
|
|
22782
|
+
eventBus.emit(`UI:${playAgainEvent}`, {});
|
|
22783
|
+
}
|
|
22784
|
+
}, [playAgainEvent, eventBus]);
|
|
22710
22785
|
const ctx = useMemo(
|
|
22711
22786
|
() => ({
|
|
22712
22787
|
hoveredTile,
|
|
@@ -22715,11 +22790,18 @@ function CastleBoard({
|
|
|
22715
22790
|
selectedFeature,
|
|
22716
22791
|
clearSelection,
|
|
22717
22792
|
tileToScreen,
|
|
22718
|
-
scale
|
|
22793
|
+
scale,
|
|
22794
|
+
gold,
|
|
22795
|
+
health,
|
|
22796
|
+
maxHealth,
|
|
22797
|
+
wave,
|
|
22798
|
+
tickCount,
|
|
22799
|
+
buildings,
|
|
22800
|
+
result
|
|
22719
22801
|
}),
|
|
22720
|
-
[hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
|
|
22802
|
+
[hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
|
|
22721
22803
|
);
|
|
22722
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
|
|
22804
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
|
|
22723
22805
|
header && header(ctx),
|
|
22724
22806
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
22725
22807
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
|
|
@@ -22744,7 +22826,29 @@ function CastleBoard({
|
|
|
22744
22826
|
] }),
|
|
22745
22827
|
sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
|
|
22746
22828
|
] }),
|
|
22747
|
-
footer && footer(ctx)
|
|
22829
|
+
footer && footer(ctx),
|
|
22830
|
+
result !== "none" && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
|
|
22831
|
+
/* @__PURE__ */ jsx(
|
|
22832
|
+
Typography,
|
|
22833
|
+
{
|
|
22834
|
+
variant: "h2",
|
|
22835
|
+
className: cn(
|
|
22836
|
+
"text-4xl font-black tracking-widest uppercase",
|
|
22837
|
+
result === "victory" ? "text-warning" : "text-error"
|
|
22838
|
+
),
|
|
22839
|
+
children: result === "victory" ? t("battle.victory") : t("battle.defeat")
|
|
22840
|
+
}
|
|
22841
|
+
),
|
|
22842
|
+
/* @__PURE__ */ jsx(
|
|
22843
|
+
Button,
|
|
22844
|
+
{
|
|
22845
|
+
variant: "primary",
|
|
22846
|
+
className: "px-8 py-3 font-semibold",
|
|
22847
|
+
onClick: handlePlayAgain,
|
|
22848
|
+
children: t("battle.playAgain")
|
|
22849
|
+
}
|
|
22850
|
+
)
|
|
22851
|
+
] }) })
|
|
22748
22852
|
] });
|
|
22749
22853
|
}
|
|
22750
22854
|
var init_CastleBoard = __esm({
|
|
@@ -22752,6 +22856,10 @@ var init_CastleBoard = __esm({
|
|
|
22752
22856
|
"use client";
|
|
22753
22857
|
init_cn();
|
|
22754
22858
|
init_useEventBus();
|
|
22859
|
+
init_Box();
|
|
22860
|
+
init_Button();
|
|
22861
|
+
init_Typography();
|
|
22862
|
+
init_Stack();
|
|
22755
22863
|
init_IsometricCanvas();
|
|
22756
22864
|
init_boardEntity();
|
|
22757
22865
|
init_isometric();
|
|
@@ -23628,22 +23736,16 @@ function ClassifierBoard({
|
|
|
23628
23736
|
const { emit } = useEventBus();
|
|
23629
23737
|
const { t } = useTranslate();
|
|
23630
23738
|
const resolved = boardEntity(entity);
|
|
23631
|
-
const [localAssignments, setLocalAssignments] = useState({});
|
|
23632
23739
|
const [headerError, setHeaderError] = useState(false);
|
|
23633
|
-
const [localSubmitted, setLocalSubmitted] = useState(false);
|
|
23634
|
-
const [localAttempts, setLocalAttempts] = useState(0);
|
|
23635
|
-
const [showHint, setShowHint] = useState(false);
|
|
23636
23740
|
const items = Array.isArray(resolved?.items) ? resolved.items : [];
|
|
23637
23741
|
const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
|
|
23638
|
-
const
|
|
23639
|
-
const
|
|
23640
|
-
const
|
|
23641
|
-
const assignments =
|
|
23642
|
-
if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
|
|
23742
|
+
const result = str(resolved?.result);
|
|
23743
|
+
const submitted = result === "win";
|
|
23744
|
+
const attempts = num(resolved?.attempts);
|
|
23745
|
+
const assignments = items.reduce((acc, item) => {
|
|
23746
|
+
if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
|
|
23643
23747
|
return acc;
|
|
23644
|
-
}, {})
|
|
23645
|
-
const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
|
|
23646
|
-
const submitted = entityDrivesResult || localSubmitted;
|
|
23748
|
+
}, {});
|
|
23647
23749
|
const unassignedItems = items.filter((item) => !assignments[item.id]);
|
|
23648
23750
|
const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
|
|
23649
23751
|
const results = submitted ? items.map((item) => ({
|
|
@@ -23651,54 +23753,25 @@ function ClassifierBoard({
|
|
|
23651
23753
|
assigned: assignments[item.id],
|
|
23652
23754
|
correct: assignments[item.id] === item.correctCategory
|
|
23653
23755
|
})) : [];
|
|
23654
|
-
const allCorrect =
|
|
23756
|
+
const allCorrect = result === "win";
|
|
23655
23757
|
const correctCount = results.filter((r2) => r2.correct).length;
|
|
23758
|
+
const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
|
|
23656
23759
|
const handleAssign = (itemId, categoryId) => {
|
|
23657
23760
|
if (submitted) return;
|
|
23658
|
-
if (assignEvent) {
|
|
23659
|
-
emit(`UI:${assignEvent}`, { itemId, categoryId });
|
|
23660
|
-
}
|
|
23661
|
-
if (!entityHasAssignments) {
|
|
23662
|
-
setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
|
|
23663
|
-
}
|
|
23761
|
+
if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
|
|
23664
23762
|
};
|
|
23665
23763
|
const handleUnassign = (itemId) => {
|
|
23666
23764
|
if (submitted) return;
|
|
23667
|
-
if (
|
|
23668
|
-
setLocalAssignments((prev) => {
|
|
23669
|
-
const next = { ...prev };
|
|
23670
|
-
delete next[itemId];
|
|
23671
|
-
return next;
|
|
23672
|
-
});
|
|
23673
|
-
}
|
|
23765
|
+
if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
|
|
23674
23766
|
};
|
|
23675
|
-
const handleSubmit =
|
|
23676
|
-
if (checkEvent) {
|
|
23677
|
-
|
|
23678
|
-
|
|
23679
|
-
if (!entityDrivesResult) {
|
|
23680
|
-
setLocalSubmitted(true);
|
|
23681
|
-
setLocalAttempts((a) => a + 1);
|
|
23682
|
-
const correct = items.every((item) => assignments[item.id] === item.correctCategory);
|
|
23683
|
-
if (correct) {
|
|
23684
|
-
emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
|
|
23685
|
-
}
|
|
23686
|
-
}
|
|
23687
|
-
}, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
|
|
23688
|
-
const handleReset = () => {
|
|
23689
|
-
if (!entityDrivesResult) setLocalSubmitted(false);
|
|
23690
|
-
if (attempts >= 2 && str(resolved?.hint)) {
|
|
23691
|
-
setShowHint(true);
|
|
23767
|
+
const handleSubmit = () => {
|
|
23768
|
+
if (checkEvent) emit(`UI:${checkEvent}`, {});
|
|
23769
|
+
if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
|
|
23770
|
+
emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
|
|
23692
23771
|
}
|
|
23693
23772
|
};
|
|
23694
23773
|
const handleFullReset = () => {
|
|
23695
|
-
if (playAgainEvent) {
|
|
23696
|
-
emit(`UI:${playAgainEvent}`, {});
|
|
23697
|
-
}
|
|
23698
|
-
setLocalAssignments({});
|
|
23699
|
-
setLocalSubmitted(false);
|
|
23700
|
-
setLocalAttempts(0);
|
|
23701
|
-
setShowHint(false);
|
|
23774
|
+
if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
|
|
23702
23775
|
};
|
|
23703
23776
|
if (!resolved) return null;
|
|
23704
23777
|
const theme = resolved.theme ?? void 0;
|
|
@@ -23737,17 +23810,17 @@ function ClassifierBoard({
|
|
|
23737
23810
|
/* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
|
|
23738
23811
|
] }),
|
|
23739
23812
|
/* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
|
|
23740
|
-
const
|
|
23813
|
+
const result2 = results.find((r2) => r2.item.id === item.id);
|
|
23741
23814
|
return /* @__PURE__ */ jsxs(
|
|
23742
23815
|
Badge,
|
|
23743
23816
|
{
|
|
23744
23817
|
size: "sm",
|
|
23745
|
-
className: `cursor-pointer ${
|
|
23818
|
+
className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
|
|
23746
23819
|
onClick: () => handleUnassign(item.id),
|
|
23747
23820
|
children: [
|
|
23748
23821
|
item.iconUrl && /* @__PURE__ */ jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
|
|
23749
23822
|
item.label,
|
|
23750
|
-
|
|
23823
|
+
result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
|
|
23751
23824
|
]
|
|
23752
23825
|
},
|
|
23753
23826
|
item.id
|
|
@@ -23776,10 +23849,10 @@ function ClassifierBoard({
|
|
|
23776
23849
|
] }) }),
|
|
23777
23850
|
showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
|
|
23778
23851
|
/* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
|
|
23779
|
-
!submitted
|
|
23852
|
+
!submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
|
|
23780
23853
|
/* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
|
|
23781
23854
|
t("classifier.check")
|
|
23782
|
-
] })
|
|
23855
|
+
] }),
|
|
23783
23856
|
/* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
|
|
23784
23857
|
/* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
|
|
23785
23858
|
t("classifier.reset")
|
|
@@ -31808,7 +31881,7 @@ var init_MapView = __esm({
|
|
|
31808
31881
|
shadowSize: [41, 41]
|
|
31809
31882
|
});
|
|
31810
31883
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
31811
|
-
const { useEffect: useEffect78, useRef: useRef70, useCallback:
|
|
31884
|
+
const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback117, useState: useState115 } = React91__default;
|
|
31812
31885
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
31813
31886
|
const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
31814
31887
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
@@ -31854,7 +31927,7 @@ var init_MapView = __esm({
|
|
|
31854
31927
|
}) {
|
|
31855
31928
|
const eventBus = useEventBus3();
|
|
31856
31929
|
const [clickedPosition, setClickedPosition] = useState115(null);
|
|
31857
|
-
const handleMapClick =
|
|
31930
|
+
const handleMapClick = useCallback117((lat, lng) => {
|
|
31858
31931
|
if (showClickedPin) {
|
|
31859
31932
|
setClickedPosition({ lat, lng });
|
|
31860
31933
|
}
|
|
@@ -31863,7 +31936,7 @@ var init_MapView = __esm({
|
|
|
31863
31936
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
31864
31937
|
}
|
|
31865
31938
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
31866
|
-
const handleMarkerClick =
|
|
31939
|
+
const handleMarkerClick = useCallback117((marker) => {
|
|
31867
31940
|
onMarkerClick?.(marker);
|
|
31868
31941
|
if (markerClickEvent) {
|
|
31869
31942
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -40685,44 +40758,27 @@ function DebuggerBoard({
|
|
|
40685
40758
|
const { t } = useTranslate();
|
|
40686
40759
|
const resolved = boardEntity(entity);
|
|
40687
40760
|
const [headerError, setHeaderError] = useState(false);
|
|
40688
|
-
const [showHint, setShowHint] = useState(false);
|
|
40689
40761
|
const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
|
|
40690
|
-
const result = resolved?.result
|
|
40762
|
+
const result = str(resolved?.result) || "none";
|
|
40691
40763
|
const attempts = num(resolved?.attempts);
|
|
40692
|
-
const submitted = result
|
|
40764
|
+
const submitted = result === "win";
|
|
40765
|
+
const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
|
|
40693
40766
|
const bugLines = lines.filter((l) => l.isBug);
|
|
40694
40767
|
const flaggedLines = lines.filter((l) => l.isFlagged);
|
|
40695
40768
|
const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
|
|
40696
40769
|
const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
|
|
40697
|
-
const allCorrect =
|
|
40770
|
+
const allCorrect = submitted;
|
|
40698
40771
|
const toggleLine = (lineId) => {
|
|
40699
40772
|
if (submitted) return;
|
|
40700
|
-
if (toggleFlagEvent) {
|
|
40701
|
-
emit(`UI:${toggleFlagEvent}`, { lineId });
|
|
40702
|
-
}
|
|
40773
|
+
if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
|
|
40703
40774
|
};
|
|
40704
40775
|
const handleSubmit = useCallback(() => {
|
|
40705
|
-
if (checkEvent) {
|
|
40706
|
-
emit(`UI:${checkEvent}`, {});
|
|
40707
|
-
}
|
|
40776
|
+
if (checkEvent) emit(`UI:${checkEvent}`, {});
|
|
40708
40777
|
const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
|
|
40709
|
-
if (correct) {
|
|
40710
|
-
emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
|
|
40711
|
-
}
|
|
40778
|
+
if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
|
|
40712
40779
|
}, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
|
|
40713
40780
|
const handleReset = () => {
|
|
40714
|
-
if (playAgainEvent) {
|
|
40715
|
-
emit(`UI:${playAgainEvent}`, {});
|
|
40716
|
-
}
|
|
40717
|
-
if (attempts >= 2 && str(resolved?.hint)) {
|
|
40718
|
-
setShowHint(true);
|
|
40719
|
-
}
|
|
40720
|
-
};
|
|
40721
|
-
const handleFullReset = () => {
|
|
40722
|
-
if (playAgainEvent) {
|
|
40723
|
-
emit(`UI:${playAgainEvent}`, {});
|
|
40724
|
-
}
|
|
40725
|
-
setShowHint(false);
|
|
40781
|
+
if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
|
|
40726
40782
|
};
|
|
40727
40783
|
if (!resolved) return null;
|
|
40728
40784
|
const theme = resolved.theme ?? void 0;
|
|
@@ -40746,7 +40802,7 @@ function DebuggerBoard({
|
|
|
40746
40802
|
/* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
|
|
40747
40803
|
] }),
|
|
40748
40804
|
/* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
|
|
40749
|
-
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(
|
|
40805
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
|
|
40750
40806
|
] }) }),
|
|
40751
40807
|
/* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
|
|
40752
40808
|
const isFlagged = !!line.isFlagged;
|
|
@@ -40796,11 +40852,11 @@ function DebuggerBoard({
|
|
|
40796
40852
|
] }) }),
|
|
40797
40853
|
showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
|
|
40798
40854
|
/* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
|
|
40799
|
-
!submitted
|
|
40855
|
+
!submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
|
|
40800
40856
|
/* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
|
|
40801
40857
|
t("debugger.submit")
|
|
40802
|
-
] })
|
|
40803
|
-
/* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick:
|
|
40858
|
+
] }),
|
|
40859
|
+
/* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
|
|
40804
40860
|
/* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
|
|
40805
40861
|
t("debugger.reset")
|
|
40806
40862
|
] })
|
|
@@ -41801,39 +41857,40 @@ function EventHandlerBoard({
|
|
|
41801
41857
|
stepDurationMs = 800,
|
|
41802
41858
|
playEvent,
|
|
41803
41859
|
completeEvent,
|
|
41860
|
+
editRuleEvent,
|
|
41861
|
+
playAgainEvent,
|
|
41804
41862
|
className
|
|
41805
41863
|
}) {
|
|
41806
41864
|
const { emit } = useEventBus();
|
|
41807
41865
|
const { t } = useTranslate();
|
|
41808
41866
|
const resolved = boardEntity(entity);
|
|
41809
|
-
const
|
|
41810
|
-
const
|
|
41867
|
+
const objects = rows(resolved?.objects);
|
|
41868
|
+
const entityResult = str(resolved?.result) || "none";
|
|
41869
|
+
const isSuccess = entityResult === "win";
|
|
41870
|
+
const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
|
|
41811
41871
|
const [selectedObjectId, setSelectedObjectId] = useState(
|
|
41812
|
-
|
|
41872
|
+
objects[0] ? objId(objects[0]) : null
|
|
41813
41873
|
);
|
|
41814
41874
|
const [headerError, setHeaderError] = useState(false);
|
|
41815
|
-
const [
|
|
41875
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
41816
41876
|
const [eventLog, setEventLog] = useState([]);
|
|
41817
|
-
const [attempts, setAttempts] = useState(0);
|
|
41818
41877
|
const timerRef = useRef(null);
|
|
41819
41878
|
const logIdCounter = useRef(0);
|
|
41820
41879
|
useEffect(() => () => {
|
|
41821
41880
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
41822
41881
|
}, []);
|
|
41823
|
-
const selectedObject = objects.find((o) => objId(o) === selectedObjectId)
|
|
41882
|
+
const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
|
|
41824
41883
|
const handleRulesChange = useCallback((objectId, rules) => {
|
|
41825
|
-
|
|
41826
|
-
|
|
41827
|
-
));
|
|
41828
|
-
}, []);
|
|
41884
|
+
if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
|
|
41885
|
+
}, [editRuleEvent, emit]);
|
|
41829
41886
|
const addLogEntry = useCallback((icon, message, status = "done") => {
|
|
41830
41887
|
const id = `log-${logIdCounter.current++}`;
|
|
41831
41888
|
setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
|
|
41832
41889
|
}, []);
|
|
41833
41890
|
const handlePlay = useCallback(() => {
|
|
41834
|
-
if (
|
|
41891
|
+
if (isPlaying || isSuccess) return;
|
|
41835
41892
|
if (playEvent) emit(`UI:${playEvent}`, {});
|
|
41836
|
-
|
|
41893
|
+
setIsPlaying(true);
|
|
41837
41894
|
setEventLog([]);
|
|
41838
41895
|
const allRules = [];
|
|
41839
41896
|
objects.forEach((obj) => {
|
|
@@ -41849,15 +41906,8 @@ function EventHandlerBoard({
|
|
|
41849
41906
|
let goalReached = false;
|
|
41850
41907
|
const processNext = () => {
|
|
41851
41908
|
if (eventQueue.length === 0 || stepIdx > 20) {
|
|
41852
|
-
|
|
41853
|
-
|
|
41854
|
-
if (completeEvent) {
|
|
41855
|
-
emit(`UI:${completeEvent}`, { success: true });
|
|
41856
|
-
}
|
|
41857
|
-
} else {
|
|
41858
|
-
setAttempts((prev) => prev + 1);
|
|
41859
|
-
setPlayState("fail");
|
|
41860
|
-
}
|
|
41909
|
+
setIsPlaying(false);
|
|
41910
|
+
if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
|
|
41861
41911
|
return;
|
|
41862
41912
|
}
|
|
41863
41913
|
const currentEvent = eventQueue.shift();
|
|
@@ -41888,21 +41938,19 @@ function EventHandlerBoard({
|
|
|
41888
41938
|
addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
|
|
41889
41939
|
}
|
|
41890
41940
|
timerRef.current = setTimeout(processNext, stepDurationMs);
|
|
41891
|
-
}, [
|
|
41941
|
+
}, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
|
|
41892
41942
|
const handleTryAgain = useCallback(() => {
|
|
41893
41943
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
41894
|
-
|
|
41944
|
+
setIsPlaying(false);
|
|
41895
41945
|
setEventLog([]);
|
|
41896
41946
|
}, []);
|
|
41897
41947
|
const handleReset = useCallback(() => {
|
|
41898
41948
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
41899
|
-
|
|
41900
|
-
setObjects([...resetObjects]);
|
|
41901
|
-
setPlayState("editing");
|
|
41949
|
+
setIsPlaying(false);
|
|
41902
41950
|
setEventLog([]);
|
|
41903
|
-
setSelectedObjectId(
|
|
41904
|
-
|
|
41905
|
-
}, [
|
|
41951
|
+
setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
|
|
41952
|
+
if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
|
|
41953
|
+
}, [objects, playAgainEvent, emit]);
|
|
41906
41954
|
if (!resolved) return null;
|
|
41907
41955
|
const objectViewers = objects.map((obj) => {
|
|
41908
41956
|
const states = objStates(obj);
|
|
@@ -41971,12 +42019,12 @@ function EventHandlerBoard({
|
|
|
41971
42019
|
{
|
|
41972
42020
|
object: selectedObject,
|
|
41973
42021
|
onRulesChange: handleRulesChange,
|
|
41974
|
-
disabled:
|
|
42022
|
+
disabled: isPlaying
|
|
41975
42023
|
}
|
|
41976
42024
|
),
|
|
41977
42025
|
eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
|
|
41978
|
-
|
|
41979
|
-
|
|
42026
|
+
isSuccess && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
|
|
42027
|
+
!isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
41980
42028
|
/* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(encourageKey) }) }),
|
|
41981
42029
|
showHint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
|
|
41982
42030
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
|
|
@@ -41984,12 +42032,12 @@ function EventHandlerBoard({
|
|
|
41984
42032
|
] }) })
|
|
41985
42033
|
] }),
|
|
41986
42034
|
/* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
|
|
41987
|
-
|
|
42035
|
+
!isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
|
|
41988
42036
|
Button,
|
|
41989
42037
|
{
|
|
41990
42038
|
variant: "primary",
|
|
41991
42039
|
onClick: handlePlay,
|
|
41992
|
-
disabled:
|
|
42040
|
+
disabled: isPlaying || isSuccess,
|
|
41993
42041
|
children: "\u25B6 " + t("game.play")
|
|
41994
42042
|
}
|
|
41995
42043
|
),
|
|
@@ -44542,21 +44590,6 @@ var init_ModalSlot = __esm({
|
|
|
44542
44590
|
ModalSlot.displayName = "ModalSlot";
|
|
44543
44591
|
}
|
|
44544
44592
|
});
|
|
44545
|
-
function getOpponentAction(strategy, actions, history) {
|
|
44546
|
-
const actionIds = actions.map((a) => a.id);
|
|
44547
|
-
switch (strategy) {
|
|
44548
|
-
case "always-cooperate":
|
|
44549
|
-
return actionIds[0];
|
|
44550
|
-
case "always-defect":
|
|
44551
|
-
return actionIds[actionIds.length - 1];
|
|
44552
|
-
case "tit-for-tat":
|
|
44553
|
-
if (history.length === 0) return actionIds[0];
|
|
44554
|
-
return history[history.length - 1].playerAction;
|
|
44555
|
-
case "random":
|
|
44556
|
-
default:
|
|
44557
|
-
return actionIds[Math.floor(Math.random() * actionIds.length)];
|
|
44558
|
-
}
|
|
44559
|
-
}
|
|
44560
44593
|
function NegotiatorBoard({
|
|
44561
44594
|
entity,
|
|
44562
44595
|
completeEvent = "PUZZLE_COMPLETE",
|
|
@@ -44578,28 +44611,35 @@ function NegotiatorBoard({
|
|
|
44578
44611
|
const playerTotal = num(resolved?.score);
|
|
44579
44612
|
const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
|
|
44580
44613
|
const won = result === "win";
|
|
44581
|
-
const
|
|
44614
|
+
const lastPlayerAction = str(resolved?.lastPlayerAction);
|
|
44615
|
+
const lastOpponentAction = str(resolved?.lastOpponentAction);
|
|
44616
|
+
const lastPayoff = num(resolved?.lastPayoff);
|
|
44582
44617
|
const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
|
|
44583
44618
|
const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
|
|
44619
|
+
const prevRoundRef = useRef(currentRound);
|
|
44620
|
+
useEffect(() => {
|
|
44621
|
+
if (currentRound > prevRoundRef.current && lastPlayerAction) {
|
|
44622
|
+
const opponentPayoffEntry = payoffMatrix.find(
|
|
44623
|
+
(p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
|
|
44624
|
+
);
|
|
44625
|
+
setHistory((prev) => [
|
|
44626
|
+
...prev,
|
|
44627
|
+
{
|
|
44628
|
+
round: currentRound,
|
|
44629
|
+
playerAction: lastPlayerAction,
|
|
44630
|
+
opponentAction: lastOpponentAction,
|
|
44631
|
+
playerPayoff: lastPayoff,
|
|
44632
|
+
opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
|
|
44633
|
+
}
|
|
44634
|
+
]);
|
|
44635
|
+
}
|
|
44636
|
+
prevRoundRef.current = currentRound;
|
|
44637
|
+
}, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
|
|
44638
|
+
const opponentTotal = useMemo(() => history.reduce((s, r2) => s + r2.opponentPayoff, 0), [history]);
|
|
44584
44639
|
const handleAction = useCallback((actionId) => {
|
|
44585
44640
|
if (isComplete) return;
|
|
44586
|
-
const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
|
|
44587
|
-
const payoff = payoffMatrix.find(
|
|
44588
|
-
(p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
|
|
44589
|
-
);
|
|
44590
|
-
const playerPayoff = payoff?.playerPayoff ?? 0;
|
|
44591
|
-
setHistory((prev) => [
|
|
44592
|
-
...prev,
|
|
44593
|
-
{
|
|
44594
|
-
round: prev.length + 1,
|
|
44595
|
-
playerAction: actionId,
|
|
44596
|
-
opponentAction,
|
|
44597
|
-
playerPayoff,
|
|
44598
|
-
opponentPayoff: payoff?.opponentPayoff ?? 0
|
|
44599
|
-
}
|
|
44600
|
-
]);
|
|
44601
44641
|
if (playRoundEvent) {
|
|
44602
|
-
emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff:
|
|
44642
|
+
emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
|
|
44603
44643
|
}
|
|
44604
44644
|
if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
|
|
44605
44645
|
if (finishEvent) {
|
|
@@ -44609,10 +44649,11 @@ function NegotiatorBoard({
|
|
|
44609
44649
|
setShowHint(true);
|
|
44610
44650
|
}
|
|
44611
44651
|
}
|
|
44612
|
-
}, [isComplete, resolved, totalRounds, currentRound,
|
|
44652
|
+
}, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
|
|
44613
44653
|
const handleReset = useCallback(() => {
|
|
44614
44654
|
setHistory([]);
|
|
44615
44655
|
setShowHint(false);
|
|
44656
|
+
prevRoundRef.current = 0;
|
|
44616
44657
|
if (playAgainEvent) {
|
|
44617
44658
|
emit(`UI:${playAgainEvent}`, {});
|
|
44618
44659
|
}
|
|
@@ -46967,17 +47008,24 @@ function SequencerBoard({
|
|
|
46967
47008
|
const { emit } = useEventBus();
|
|
46968
47009
|
const { t } = useTranslate();
|
|
46969
47010
|
const resolved = boardEntity(entity);
|
|
46970
|
-
const maxSlots = num(resolved?.maxSlots);
|
|
47011
|
+
const maxSlots = num(resolved?.maxSlots) || 3;
|
|
46971
47012
|
const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
|
|
46972
47013
|
const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
|
|
46973
47014
|
const allowDuplicates = resolved?.allowDuplicates !== false;
|
|
47015
|
+
const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
|
|
47016
|
+
const entityResult = str(resolved?.result);
|
|
47017
|
+
const entityAttempts = num(resolved?.attempts);
|
|
47018
|
+
const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
|
|
47019
|
+
const slots = Array.from({ length: maxSlots }, (_, i) => {
|
|
47020
|
+
const entitySlot = entitySlots.find((s) => s.index === i);
|
|
47021
|
+
if (!entitySlot?.placedActionId) return void 0;
|
|
47022
|
+
return availableActions.find((a) => a.id === entitySlot.placedActionId);
|
|
47023
|
+
});
|
|
47024
|
+
const isSuccess = entityResult === "win";
|
|
47025
|
+
const attempts = entityAttempts;
|
|
47026
|
+
const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
|
|
47027
|
+
const currentStep = entityCurrentStep;
|
|
46974
47028
|
const [headerError, setHeaderError] = useState(false);
|
|
46975
|
-
const [slots, setSlots] = useState(
|
|
46976
|
-
() => Array.from({ length: maxSlots }, () => void 0)
|
|
46977
|
-
);
|
|
46978
|
-
const [playState, setPlayState] = useState("idle");
|
|
46979
|
-
const [currentStep, setCurrentStep] = useState(-1);
|
|
46980
|
-
const [attempts, setAttempts] = useState(0);
|
|
46981
47029
|
const [slotFeedback, setSlotFeedback] = useState(
|
|
46982
47030
|
() => Array.from({ length: maxSlots }, () => null)
|
|
46983
47031
|
);
|
|
@@ -46986,11 +47034,6 @@ function SequencerBoard({
|
|
|
46986
47034
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
46987
47035
|
}, []);
|
|
46988
47036
|
const handleSlotDrop = useCallback((index, item) => {
|
|
46989
|
-
setSlots((prev) => {
|
|
46990
|
-
const next = [...prev];
|
|
46991
|
-
next[index] = item;
|
|
46992
|
-
return next;
|
|
46993
|
-
});
|
|
46994
47037
|
setSlotFeedback((prev) => {
|
|
46995
47038
|
const next = [...prev];
|
|
46996
47039
|
next[index] = null;
|
|
@@ -47000,11 +47043,6 @@ function SequencerBoard({
|
|
|
47000
47043
|
if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
|
|
47001
47044
|
}, [emit, placeEvent]);
|
|
47002
47045
|
const handleSlotRemove = useCallback((index) => {
|
|
47003
|
-
setSlots((prev) => {
|
|
47004
|
-
const next = [...prev];
|
|
47005
|
-
next[index] = void 0;
|
|
47006
|
-
return next;
|
|
47007
|
-
});
|
|
47008
47046
|
setSlotFeedback((prev) => {
|
|
47009
47047
|
const next = [...prev];
|
|
47010
47048
|
next[index] = null;
|
|
@@ -47015,48 +47053,34 @@ function SequencerBoard({
|
|
|
47015
47053
|
}, [emit, removeEvent]);
|
|
47016
47054
|
const handleReset = useCallback(() => {
|
|
47017
47055
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
47018
|
-
setSlots(Array.from({ length: maxSlots }, () => void 0));
|
|
47019
|
-
setPlayState("idle");
|
|
47020
|
-
setCurrentStep(-1);
|
|
47021
|
-
setAttempts(0);
|
|
47022
47056
|
setSlotFeedback(Array.from({ length: maxSlots }, () => null));
|
|
47023
47057
|
if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
|
|
47024
47058
|
}, [maxSlots, playAgainEvent, emit]);
|
|
47025
47059
|
const filledSlots = slots.filter((s) => !!s);
|
|
47026
|
-
const canPlay = filledSlots.length > 0 &&
|
|
47060
|
+
const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
|
|
47027
47061
|
const handlePlay = useCallback(() => {
|
|
47028
47062
|
if (!canPlay) return;
|
|
47029
47063
|
setSlotFeedback(Array.from({ length: maxSlots }, () => null));
|
|
47030
47064
|
emit("UI:PLAY_SOUND", { key: "confirm" });
|
|
47031
47065
|
const sequence = slots.map((s) => s?.id || "");
|
|
47032
|
-
|
|
47033
|
-
|
|
47034
|
-
}
|
|
47035
|
-
setPlayState("playing");
|
|
47036
|
-
setCurrentStep(0);
|
|
47066
|
+
const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
|
|
47067
|
+
if (playEvent) emit(`UI:${playEvent}`, { sequence });
|
|
47037
47068
|
let step = 0;
|
|
47038
47069
|
const advance = () => {
|
|
47039
47070
|
step++;
|
|
47071
|
+
emit("UI:STEP", { step });
|
|
47040
47072
|
if (step >= maxSlots) {
|
|
47073
|
+
if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
|
|
47041
47074
|
const playerSeq = slots.map((s) => s?.id);
|
|
47042
|
-
const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
|
|
47043
47075
|
const success = solutions.some(
|
|
47044
47076
|
(sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
|
|
47045
47077
|
);
|
|
47046
|
-
if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
|
|
47047
47078
|
if (success) {
|
|
47048
|
-
setPlayState("success");
|
|
47049
|
-
setCurrentStep(-1);
|
|
47050
47079
|
emit("UI:PLAY_SOUND", { key: "levelComplete" });
|
|
47051
|
-
if (completeEvent) {
|
|
47052
|
-
emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
|
|
47053
|
-
}
|
|
47080
|
+
if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
|
|
47054
47081
|
} else {
|
|
47055
|
-
setAttempts((prev) => prev + 1);
|
|
47056
47082
|
const feedback = computeSlotFeedback(playerSeq, solutions);
|
|
47057
47083
|
setSlotFeedback(feedback);
|
|
47058
|
-
setPlayState("idle");
|
|
47059
|
-
setCurrentStep(-1);
|
|
47060
47084
|
emit("UI:PLAY_SOUND", { key: "fail" });
|
|
47061
47085
|
const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
|
|
47062
47086
|
for (let ci = 0; ci < correctCount2; ci++) {
|
|
@@ -47066,7 +47090,6 @@ function SequencerBoard({
|
|
|
47066
47090
|
}
|
|
47067
47091
|
}
|
|
47068
47092
|
} else {
|
|
47069
|
-
setCurrentStep(step);
|
|
47070
47093
|
timerRef.current = setTimeout(advance, stepDurationMs);
|
|
47071
47094
|
}
|
|
47072
47095
|
};
|
|
@@ -47116,7 +47139,7 @@ function SequencerBoard({
|
|
|
47116
47139
|
/* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
47117
47140
|
/* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
|
|
47118
47141
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
|
|
47119
|
-
hasFeedback &&
|
|
47142
|
+
hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
|
|
47120
47143
|
`${correctCount}/${maxSlots} `,
|
|
47121
47144
|
"\u2705"
|
|
47122
47145
|
] })
|
|
@@ -47128,7 +47151,7 @@ function SequencerBoard({
|
|
|
47128
47151
|
maxSlots,
|
|
47129
47152
|
onSlotDrop: handleSlotDrop,
|
|
47130
47153
|
onSlotRemove: handleSlotRemove,
|
|
47131
|
-
playing:
|
|
47154
|
+
playing: isPlayingBack,
|
|
47132
47155
|
currentStep,
|
|
47133
47156
|
categoryColors,
|
|
47134
47157
|
slotFeedback,
|
|
@@ -47136,7 +47159,7 @@ function SequencerBoard({
|
|
|
47136
47159
|
}
|
|
47137
47160
|
)
|
|
47138
47161
|
] }),
|
|
47139
|
-
|
|
47162
|
+
!isPlayingBack && /* @__PURE__ */ jsx(
|
|
47140
47163
|
ActionPalette,
|
|
47141
47164
|
{
|
|
47142
47165
|
actions: availableActions,
|
|
@@ -47146,8 +47169,8 @@ function SequencerBoard({
|
|
|
47146
47169
|
label: t("sequencer.dragActions")
|
|
47147
47170
|
}
|
|
47148
47171
|
),
|
|
47149
|
-
hasFeedback &&
|
|
47150
|
-
|
|
47172
|
+
hasFeedback && !isPlayingBack && !isSuccess && attempts > 0 && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
|
|
47173
|
+
isSuccess && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
|
|
47151
47174
|
/* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
|
|
47152
47175
|
/* @__PURE__ */ jsx(
|
|
47153
47176
|
Button,
|
|
@@ -48197,6 +48220,9 @@ function StateArchitectBoard({
|
|
|
48197
48220
|
stepDurationMs = 600,
|
|
48198
48221
|
testEvent,
|
|
48199
48222
|
completeEvent,
|
|
48223
|
+
addTransitionEvent,
|
|
48224
|
+
removeTransitionEvent,
|
|
48225
|
+
playAgainEvent,
|
|
48200
48226
|
className
|
|
48201
48227
|
}) {
|
|
48202
48228
|
const { emit } = useEventBus();
|
|
@@ -48209,14 +48235,16 @@ function StateArchitectBoard({
|
|
|
48209
48235
|
const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
|
|
48210
48236
|
const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
|
|
48211
48237
|
const entityVariables = rows(resolved?.variables);
|
|
48212
|
-
const
|
|
48238
|
+
const transitions = entityTransitions;
|
|
48239
|
+
const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
|
|
48240
|
+
const entityResult = str(resolved?.result) || "none";
|
|
48241
|
+
const isSuccess = entityResult === "win";
|
|
48242
|
+
const [isTesting, setIsTesting] = useState(false);
|
|
48213
48243
|
const [headerError, setHeaderError] = useState(false);
|
|
48214
|
-
const [playState, setPlayState] = useState("editing");
|
|
48215
48244
|
const [currentState, setCurrentState] = useState(initialState);
|
|
48216
48245
|
const [selectedState, setSelectedState] = useState(null);
|
|
48217
48246
|
const [testResults, setTestResults] = useState([]);
|
|
48218
48247
|
const [variables, setVariables] = useState(() => [...entityVariables]);
|
|
48219
|
-
const [attempts, setAttempts] = useState(0);
|
|
48220
48248
|
const timerRef = useRef(null);
|
|
48221
48249
|
const [addingFrom, setAddingFrom] = useState(null);
|
|
48222
48250
|
useEffect(() => () => {
|
|
@@ -48226,7 +48254,7 @@ function StateArchitectBoard({
|
|
|
48226
48254
|
const GRAPH_H = 400;
|
|
48227
48255
|
const positions = useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
|
|
48228
48256
|
const handleStateClick = useCallback((state) => {
|
|
48229
|
-
if (
|
|
48257
|
+
if (isTesting) return;
|
|
48230
48258
|
if (addingFrom) {
|
|
48231
48259
|
if (addingFrom !== state) {
|
|
48232
48260
|
const event = availableEvents[0] || "EVENT";
|
|
@@ -48236,20 +48264,20 @@ function StateArchitectBoard({
|
|
|
48236
48264
|
to: state,
|
|
48237
48265
|
event
|
|
48238
48266
|
};
|
|
48239
|
-
|
|
48267
|
+
if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
|
|
48240
48268
|
}
|
|
48241
48269
|
setAddingFrom(null);
|
|
48242
48270
|
} else {
|
|
48243
48271
|
setSelectedState(state);
|
|
48244
48272
|
}
|
|
48245
|
-
}, [
|
|
48273
|
+
}, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
|
|
48246
48274
|
const handleStartAddTransition = useCallback(() => {
|
|
48247
48275
|
if (!selectedState) return;
|
|
48248
48276
|
setAddingFrom(selectedState);
|
|
48249
48277
|
}, [selectedState]);
|
|
48250
48278
|
const handleRemoveTransition = useCallback((transId) => {
|
|
48251
|
-
|
|
48252
|
-
}, []);
|
|
48279
|
+
if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
|
|
48280
|
+
}, [removeTransitionEvent, emit]);
|
|
48253
48281
|
const machine = useMemo(() => ({
|
|
48254
48282
|
name: entityName,
|
|
48255
48283
|
description: str(resolved?.description),
|
|
@@ -48263,16 +48291,16 @@ function StateArchitectBoard({
|
|
|
48263
48291
|
}))
|
|
48264
48292
|
}), [entityName, resolved, entityStates, currentState, transitions]);
|
|
48265
48293
|
const handleTest = useCallback(() => {
|
|
48266
|
-
if (
|
|
48294
|
+
if (isTesting) return;
|
|
48267
48295
|
if (testEvent) emit(`UI:${testEvent}`, {});
|
|
48268
|
-
|
|
48296
|
+
setIsTesting(true);
|
|
48269
48297
|
setTestResults([]);
|
|
48270
48298
|
const results = [];
|
|
48271
48299
|
let testIdx = 0;
|
|
48272
48300
|
const runNextTest = () => {
|
|
48273
48301
|
if (testIdx >= testCases.length) {
|
|
48274
48302
|
const allPassed = results.every((r2) => r2.passed);
|
|
48275
|
-
|
|
48303
|
+
setIsTesting(false);
|
|
48276
48304
|
setTestResults(results);
|
|
48277
48305
|
if (allPassed && completeEvent) {
|
|
48278
48306
|
emit(`UI:${completeEvent}`, {
|
|
@@ -48280,9 +48308,6 @@ function StateArchitectBoard({
|
|
|
48280
48308
|
passedTests: results.filter((r2) => r2.passed).length
|
|
48281
48309
|
});
|
|
48282
48310
|
}
|
|
48283
|
-
if (!allPassed) {
|
|
48284
|
-
setAttempts((prev) => prev + 1);
|
|
48285
|
-
}
|
|
48286
48311
|
return;
|
|
48287
48312
|
}
|
|
48288
48313
|
const testCase = testCases[testIdx];
|
|
@@ -48305,24 +48330,23 @@ function StateArchitectBoard({
|
|
|
48305
48330
|
timerRef.current = setTimeout(runNextTest, stepDurationMs);
|
|
48306
48331
|
};
|
|
48307
48332
|
timerRef.current = setTimeout(runNextTest, stepDurationMs);
|
|
48308
|
-
}, [
|
|
48333
|
+
}, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
|
|
48309
48334
|
const handleTryAgain = useCallback(() => {
|
|
48310
48335
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
48311
|
-
|
|
48336
|
+
setIsTesting(false);
|
|
48312
48337
|
setCurrentState(initialState);
|
|
48313
48338
|
setTestResults([]);
|
|
48314
48339
|
}, [initialState]);
|
|
48315
48340
|
const handleReset = useCallback(() => {
|
|
48316
48341
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
48317
|
-
|
|
48318
|
-
setPlayState("editing");
|
|
48342
|
+
setIsTesting(false);
|
|
48319
48343
|
setCurrentState(initialState);
|
|
48320
48344
|
setTestResults([]);
|
|
48321
48345
|
setVariables([...entityVariables]);
|
|
48322
48346
|
setSelectedState(null);
|
|
48323
48347
|
setAddingFrom(null);
|
|
48324
|
-
|
|
48325
|
-
}, [
|
|
48348
|
+
if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
|
|
48349
|
+
}, [initialState, entityVariables, playAgainEvent, emit]);
|
|
48326
48350
|
const codeData = useMemo(() => ({
|
|
48327
48351
|
name: entityName,
|
|
48328
48352
|
states: entityStates,
|
|
@@ -48418,7 +48442,7 @@ function StateArchitectBoard({
|
|
|
48418
48442
|
]
|
|
48419
48443
|
}
|
|
48420
48444
|
),
|
|
48421
|
-
|
|
48445
|
+
!isTesting && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
|
|
48422
48446
|
Button,
|
|
48423
48447
|
{
|
|
48424
48448
|
variant: "ghost",
|
|
@@ -48440,7 +48464,7 @@ function StateArchitectBoard({
|
|
|
48440
48464
|
t2.guardHint,
|
|
48441
48465
|
")"
|
|
48442
48466
|
] }),
|
|
48443
|
-
|
|
48467
|
+
!isTesting && /* @__PURE__ */ jsx(
|
|
48444
48468
|
Button,
|
|
48445
48469
|
{
|
|
48446
48470
|
variant: "ghost",
|
|
@@ -48472,21 +48496,21 @@ function StateArchitectBoard({
|
|
|
48472
48496
|
resolved.showCodeView !== false && /* @__PURE__ */ jsx(StateJsonView, { data: codeData, label: "View Code" })
|
|
48473
48497
|
] })
|
|
48474
48498
|
] }),
|
|
48475
|
-
|
|
48476
|
-
|
|
48499
|
+
isSuccess && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
|
|
48500
|
+
!isTesting && !isSuccess && testResults.some((r2) => !r2.passed) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
48477
48501
|
/* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(ENCOURAGEMENT_KEYS3[Math.min(attempts - 1, ENCOURAGEMENT_KEYS3.length - 1)] ?? ENCOURAGEMENT_KEYS3[0]) }) }),
|
|
48478
|
-
attempts >=
|
|
48502
|
+
!isSuccess && attempts >= 2 && Boolean(str(resolved?.hint)) && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
|
|
48479
48503
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
|
|
48480
48504
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
|
|
48481
48505
|
] }) })
|
|
48482
48506
|
] }),
|
|
48483
48507
|
/* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
|
|
48484
|
-
|
|
48508
|
+
!isTesting && !isSuccess && testResults.some((r2) => !r2.passed) ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
|
|
48485
48509
|
Button,
|
|
48486
48510
|
{
|
|
48487
48511
|
variant: "primary",
|
|
48488
48512
|
onClick: handleTest,
|
|
48489
|
-
disabled:
|
|
48513
|
+
disabled: isTesting,
|
|
48490
48514
|
children: "\u25B6 " + t("game.runTests")
|
|
48491
48515
|
}
|
|
48492
48516
|
),
|
|
@@ -49190,17 +49214,12 @@ var init_UncontrolledBattleBoard = __esm({
|
|
|
49190
49214
|
}
|
|
49191
49215
|
});
|
|
49192
49216
|
function heroPosition(h) {
|
|
49217
|
+
if ("position" in h && h.position != null) return h.position;
|
|
49193
49218
|
return vec2(h.position);
|
|
49194
49219
|
}
|
|
49195
|
-
function heroOwner(h) {
|
|
49196
|
-
return str(h.owner);
|
|
49197
|
-
}
|
|
49198
49220
|
function heroMovement(h) {
|
|
49199
49221
|
return num(h.movement);
|
|
49200
49222
|
}
|
|
49201
|
-
function hexPassable(h) {
|
|
49202
|
-
return h.passable !== false;
|
|
49203
|
-
}
|
|
49204
49223
|
function defaultIsInRange(from, to, range) {
|
|
49205
49224
|
return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
|
|
49206
49225
|
}
|
|
@@ -49236,38 +49255,41 @@ function WorldMapBoard({
|
|
|
49236
49255
|
}) {
|
|
49237
49256
|
const eventBus = useEventBus();
|
|
49238
49257
|
const resolved = boardEntity(entity);
|
|
49239
|
-
const
|
|
49240
|
-
const
|
|
49258
|
+
const entityUnits = rows(resolved?.units);
|
|
49259
|
+
const entityTiles = rows(resolved?.tiles);
|
|
49241
49260
|
const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
|
|
49242
49261
|
const selectedHeroId = resolved?.selectedHeroId ?? null;
|
|
49243
49262
|
const assetManifest = propAssetManifest ?? resolved?.assetManifest;
|
|
49244
49263
|
const backgroundImage = resolved?.backgroundImage;
|
|
49245
49264
|
const [hoveredTile, setHoveredTile] = useState(null);
|
|
49246
|
-
const selectedHero = useMemo(
|
|
49247
|
-
() => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
|
|
49248
|
-
[heroes, selectedHeroId]
|
|
49249
|
-
);
|
|
49250
49265
|
const derivedTiles = useMemo(
|
|
49251
|
-
() =>
|
|
49252
|
-
x: num(
|
|
49253
|
-
y: num(
|
|
49254
|
-
terrain: str(
|
|
49255
|
-
terrainSprite:
|
|
49266
|
+
() => entityTiles.map((t) => ({
|
|
49267
|
+
x: num(t.x),
|
|
49268
|
+
y: num(t.y),
|
|
49269
|
+
terrain: str(t.terrain),
|
|
49270
|
+
terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
|
|
49271
|
+
passable: t.passable !== false
|
|
49256
49272
|
})),
|
|
49257
|
-
[
|
|
49273
|
+
[entityTiles]
|
|
49258
49274
|
);
|
|
49259
49275
|
const tiles = propTiles ?? derivedTiles;
|
|
49260
49276
|
const baseUnits = useMemo(
|
|
49261
|
-
() => propUnits ??
|
|
49262
|
-
id: str(
|
|
49263
|
-
position: heroPosition(
|
|
49264
|
-
name: str(
|
|
49265
|
-
|
|
49266
|
-
|
|
49267
|
-
|
|
49268
|
-
|
|
49277
|
+
() => propUnits ?? entityUnits.map((u) => ({
|
|
49278
|
+
id: str(u.id),
|
|
49279
|
+
position: heroPosition(u),
|
|
49280
|
+
name: str(u.name),
|
|
49281
|
+
// lolo uses `team` field (not `owner`)
|
|
49282
|
+
team: str(u.team) === "enemy" ? "enemy" : "player",
|
|
49283
|
+
health: num(u.health) || 100,
|
|
49284
|
+
maxHealth: num(u.maxHealth) || 100,
|
|
49285
|
+
sprite: u.sprite == null ? void 0 : str(u.sprite)
|
|
49269
49286
|
})),
|
|
49270
|
-
[
|
|
49287
|
+
[entityUnits, propUnits]
|
|
49288
|
+
);
|
|
49289
|
+
const gameUnits = baseUnits;
|
|
49290
|
+
const selectedHero = useMemo(
|
|
49291
|
+
() => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
|
|
49292
|
+
[gameUnits, selectedHeroId]
|
|
49271
49293
|
);
|
|
49272
49294
|
const MOVE_SPEED_MS_PER_TILE = 300;
|
|
49273
49295
|
const movementAnimRef = useRef(null);
|
|
@@ -49313,51 +49335,50 @@ function WorldMapBoard({
|
|
|
49313
49335
|
const validMoves = useMemo(() => {
|
|
49314
49336
|
if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
|
|
49315
49337
|
const sp = heroPosition(selectedHero);
|
|
49316
|
-
const
|
|
49338
|
+
const sTeam = str(selectedHero.team);
|
|
49317
49339
|
const range = heroMovement(selectedHero);
|
|
49318
49340
|
const moves = [];
|
|
49319
|
-
|
|
49320
|
-
const
|
|
49321
|
-
const
|
|
49322
|
-
if (
|
|
49323
|
-
if (
|
|
49324
|
-
if (!isInRange(sp, { x:
|
|
49325
|
-
if (
|
|
49326
|
-
const
|
|
49327
|
-
return
|
|
49341
|
+
tiles.forEach((t) => {
|
|
49342
|
+
const tx = t.x;
|
|
49343
|
+
const ty = t.y;
|
|
49344
|
+
if (t.passable === false) return;
|
|
49345
|
+
if (tx === sp.x && ty === sp.y) return;
|
|
49346
|
+
if (!isInRange(sp, { x: tx, y: ty }, range)) return;
|
|
49347
|
+
if (gameUnits.some((u) => {
|
|
49348
|
+
const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
|
|
49349
|
+
return up.x === tx && up.y === ty && str(u.team) === sTeam;
|
|
49328
49350
|
})) return;
|
|
49329
|
-
moves.push({ x:
|
|
49351
|
+
moves.push({ x: tx, y: ty });
|
|
49330
49352
|
});
|
|
49331
49353
|
return moves;
|
|
49332
|
-
}, [selectedHero,
|
|
49354
|
+
}, [selectedHero, tiles, gameUnits, isInRange]);
|
|
49333
49355
|
const attackTargets = useMemo(() => {
|
|
49334
49356
|
if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
|
|
49335
49357
|
const sp = heroPosition(selectedHero);
|
|
49336
|
-
const
|
|
49358
|
+
const sTeam = str(selectedHero.team);
|
|
49337
49359
|
const range = heroMovement(selectedHero);
|
|
49338
|
-
return
|
|
49339
|
-
}, [selectedHero,
|
|
49340
|
-
const maxY = Math.max(...
|
|
49360
|
+
return gameUnits.filter((u) => str(u.team) !== sTeam).filter((u) => isInRange(sp, u.position ?? { x: u.x ?? -1, y: u.y ?? -1 }, range)).map((u) => u.position ?? { x: u.x ?? -1, y: u.y ?? -1 });
|
|
49361
|
+
}, [selectedHero, gameUnits, isInRange]);
|
|
49362
|
+
const maxY = Math.max(...tiles.map((t) => t.y), 0);
|
|
49341
49363
|
const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
|
|
49342
49364
|
const tileToScreen = useCallback(
|
|
49343
49365
|
(tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
|
|
49344
49366
|
[scale, baseOffsetX]
|
|
49345
49367
|
);
|
|
49346
49368
|
const hoveredHex = useMemo(
|
|
49347
|
-
() => hoveredTile ?
|
|
49348
|
-
[hoveredTile,
|
|
49369
|
+
() => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
|
|
49370
|
+
[hoveredTile, tiles]
|
|
49349
49371
|
);
|
|
49350
49372
|
const hoveredHero = useMemo(
|
|
49351
|
-
() => hoveredTile ?
|
|
49352
|
-
const
|
|
49353
|
-
return
|
|
49373
|
+
() => hoveredTile ? gameUnits.find((u) => {
|
|
49374
|
+
const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
|
|
49375
|
+
return up.x === hoveredTile.x && up.y === hoveredTile.y;
|
|
49354
49376
|
}) ?? null : null,
|
|
49355
|
-
[hoveredTile,
|
|
49377
|
+
[hoveredTile, gameUnits]
|
|
49356
49378
|
);
|
|
49357
49379
|
const handleTileClick = useCallback((x, y) => {
|
|
49358
49380
|
if (movementAnimRef.current) return;
|
|
49359
|
-
const
|
|
49360
|
-
if (!hex) return;
|
|
49381
|
+
const tile = tiles.find((t) => t.x === x && t.y === y);
|
|
49361
49382
|
if (tileClickEvent) {
|
|
49362
49383
|
eventBus.emit(`UI:${tileClickEvent}`, { x, y });
|
|
49363
49384
|
}
|
|
@@ -49368,38 +49389,39 @@ function WorldMapBoard({
|
|
|
49368
49389
|
if (heroMoveEvent) {
|
|
49369
49390
|
eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
|
|
49370
49391
|
}
|
|
49371
|
-
const feature = str(
|
|
49392
|
+
const feature = tile ? str(tile.feature) : "";
|
|
49372
49393
|
if (feature && feature !== "none") {
|
|
49373
|
-
|
|
49394
|
+
const tileRow = tile;
|
|
49395
|
+
onFeatureEnter?.(heroId, tileRow);
|
|
49374
49396
|
if (featureEnterEvent) {
|
|
49375
|
-
eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
|
|
49397
|
+
eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
|
|
49376
49398
|
}
|
|
49377
49399
|
}
|
|
49378
49400
|
});
|
|
49379
49401
|
return;
|
|
49380
49402
|
}
|
|
49381
|
-
const enemy =
|
|
49382
|
-
const
|
|
49383
|
-
return
|
|
49403
|
+
const enemy = gameUnits.find((u) => {
|
|
49404
|
+
const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
|
|
49405
|
+
return up.x === x && up.y === y && str(u.team) === "enemy";
|
|
49384
49406
|
});
|
|
49385
49407
|
if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
|
|
49386
49408
|
const attackerId = str(selectedHero.id);
|
|
49387
|
-
const defenderId =
|
|
49409
|
+
const defenderId = enemy.id;
|
|
49388
49410
|
onBattleEncounter?.(attackerId, defenderId);
|
|
49389
49411
|
if (battleEncounterEvent) {
|
|
49390
49412
|
eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
|
|
49391
49413
|
}
|
|
49392
49414
|
}
|
|
49393
|
-
}, [
|
|
49415
|
+
}, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
|
|
49394
49416
|
const handleUnitClick = useCallback((unitId) => {
|
|
49395
|
-
const
|
|
49396
|
-
if (
|
|
49417
|
+
const unit = gameUnits.find((u) => u.id === unitId);
|
|
49418
|
+
if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
|
|
49397
49419
|
onHeroSelect?.(unitId);
|
|
49398
49420
|
if (heroSelectEvent) {
|
|
49399
49421
|
eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
|
|
49400
49422
|
}
|
|
49401
49423
|
}
|
|
49402
|
-
}, [
|
|
49424
|
+
}, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
|
|
49403
49425
|
const selectHero = useCallback((id) => {
|
|
49404
49426
|
onHeroSelect?.(id);
|
|
49405
49427
|
if (heroSelectEvent) {
|