dsh-dock 0.7.1 → 0.8.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/client.js +3571 -118
- package/features/games/games/breakout.jsx +10 -8
- package/features/games/games/gomoku.jsx +89 -20
- package/features/games/games/racer.jsx +3 -5
- package/features/games/games/shared.jsx +118 -0
- package/features/games/games/snake.jsx +58 -17
- package/features/games/games/sokoban.jsx +35 -8
- package/features/games/games/tank.jsx +15 -3
- package/features/games/games/tetris.jsx +90 -23
- package/features/games/games/xiangqi.jsx +91 -14
- package/features/games/view.jsx +32 -25
- package/features/games/xiangqi-core.js +48 -19
- package/package.json +1 -1
- package/src/client.jsx +3 -2
package/features/games/view.jsx
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// 不写入会话、不改动工作区。
|
|
6
6
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
7
7
|
import { panelNav, setPanelOpen, subscribePanel } from "../../src/shared.js";
|
|
8
|
+
import { focusStage, useGameControls } from "./games/shared.jsx";
|
|
8
9
|
import { tetrisGame } from "./games/tetris.jsx";
|
|
9
10
|
import { sokobanGame } from "./games/sokoban.jsx";
|
|
10
11
|
import { gomokuGame } from "./games/gomoku.jsx";
|
|
@@ -70,11 +71,12 @@ function DashGame(props) {
|
|
|
70
71
|
lane: Math.max(0, Math.min(LANES - 1, current.lane + delta)),
|
|
71
72
|
})), []);
|
|
72
73
|
const stageRef = useRef(null);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
if (
|
|
77
|
-
}, [
|
|
74
|
+
const onKeyDown = useCallback((event) => {
|
|
75
|
+
if (event.key === "ArrowLeft" || event.key.toLowerCase() === "a") { event.preventDefault(); move(-1); }
|
|
76
|
+
else if (event.key === "ArrowRight" || event.key.toLowerCase() === "d") { event.preventDefault(); move(1); }
|
|
77
|
+
else if (!dash.running && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); setDash(initialDash()); }
|
|
78
|
+
}, [move, dash.running]);
|
|
79
|
+
useGameControls(stageRef, props.paused, onKeyDown);
|
|
78
80
|
useEffect(() => {
|
|
79
81
|
const timer = setInterval(() => {
|
|
80
82
|
if (pausedRef.current || (typeof document !== "undefined" && document.hidden)) return;
|
|
@@ -110,11 +112,7 @@ function DashGame(props) {
|
|
|
110
112
|
}
|
|
111
113
|
return <section className="dgame-game" aria-label="星际躲避小游戏">
|
|
112
114
|
<div className="dgame-game-head"><div><h3>星际躲避</h3><p>左右移动补给艇,避开正在坠落的陨石。</p></div><div className="dgame-score"><span>得分 <strong>{dash.score}</strong></span><span aria-label={"护盾 " + dash.shield + " 格"}>护盾 {"✦".repeat(Math.max(0, dash.shield))}{"·".repeat(Math.max(0, 3 - dash.shield))}</span></div></div>
|
|
113
|
-
<div className="dgame-dash-stage" ref={stageRef} tabIndex={0} onKeyDown={(
|
|
114
|
-
if (event.key === "ArrowLeft" || event.key.toLowerCase() === "a") { event.preventDefault(); move(-1); }
|
|
115
|
-
else if (event.key === "ArrowRight" || event.key.toLowerCase() === "d") { event.preventDefault(); move(1); }
|
|
116
|
-
else if (!dash.running && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); setDash(initialDash()); }
|
|
117
|
-
}} aria-label="星际躲避游戏区域,使用左右方向键或下方按钮移动">
|
|
115
|
+
<div className="dgame-dash-stage" ref={stageRef} tabIndex={0} onKeyDown={onKeyDown} onClick={() => focusStage(stageRef)} aria-label="星际躲避游戏区域,使用左右方向键或下方按钮移动">
|
|
118
116
|
<div className="dgame-dash-grid">{cells}</div>
|
|
119
117
|
{!dash.running ? <div className="dgame-over"><strong>护盾耗尽</strong><button type="button" onClick={() => setDash(initialDash())}>重新出发</button></div> : null}
|
|
120
118
|
</div>
|
|
@@ -148,12 +146,11 @@ function ReactorGame(props) {
|
|
|
148
146
|
else if (key === "Enter" || key === " ") { event.preventDefault(); toggle(cursor); }
|
|
149
147
|
else if (key.toLowerCase() === "r") { event.preventDefault(); reset(); }
|
|
150
148
|
};
|
|
151
|
-
|
|
152
|
-
useEffect(() => { if (!props.paused && gridRef.current) gridRef.current.focus(); }, [props.paused]);
|
|
149
|
+
useGameControls(gridRef, props.paused, onKeyDown);
|
|
153
150
|
return <section className="dgame-game" aria-label="反应堆点亮小游戏">
|
|
154
151
|
<div className="dgame-game-head"><div><h3>反应堆点亮</h3><p>点一下会切换相邻模块,把九个模块全部点亮;键盘:方向键选格、Enter/空格 点亮、R 重置。</p></div><div className="dgame-score"><span>操作 <strong>{moves}</strong></span><button type="button" onClick={reset}>重置</button></div></div>
|
|
155
|
-
<div className="dgame-reactor" ref={gridRef} tabIndex={0} role="group" aria-label="九宫格反应堆" onKeyDown={onKeyDown}>
|
|
156
|
-
{cells.map((on, index) => <button type="button" key={index} tabIndex={-1} className={"dgame-reactor-cell" + (on ? " on" : "") + (cursor === index ? " focus" : "")} aria-current={cursor === index ? "true" : undefined} aria-label={(on ? "已点亮" : "未点亮") + "模块 " + (index + 1)} onClick={() => { toggle(index); setCursor(index);
|
|
152
|
+
<div className="dgame-reactor" ref={gridRef} tabIndex={0} role="group" aria-label="九宫格反应堆" onKeyDown={onKeyDown} onClick={() => focusStage(gridRef)}>
|
|
153
|
+
{cells.map((on, index) => <button type="button" key={index} tabIndex={-1} className={"dgame-reactor-cell" + (on ? " on" : "") + (cursor === index ? " focus" : "")} aria-current={cursor === index ? "true" : undefined} aria-label={(on ? "已点亮" : "未点亮") + "模块 " + (index + 1)} onClick={() => { toggle(index); setCursor(index); focusStage(gridRef); }}><i /></button>)}
|
|
157
154
|
</div>
|
|
158
155
|
<div className={"dgame-reactor-status" + (solved ? " solved" : "")}>{solved ? "反应堆稳定运行 · 做得漂亮(R 重新开始)" : "让所有模块发光,给任务一点能量"}</div>
|
|
159
156
|
</section>;
|
|
@@ -397,8 +394,8 @@ function GameWindow(props) {
|
|
|
397
394
|
y: Math.min(Math.max(8, origin.y + dy), Math.max(8, window.innerHeight - origin.h - 8)),
|
|
398
395
|
}));
|
|
399
396
|
else setWin((s) => Object.assign({}, s, {
|
|
397
|
+
// 窗口高度随内容自适应,缩放只调宽度;游戏板按比例跟随缩放。
|
|
400
398
|
w: Math.max(WIN_MIN_W, Math.min(origin.w + dx, window.innerWidth - 16)),
|
|
401
|
-
h: Math.max(WIN_MIN_H, Math.min(origin.h + dy, window.innerHeight - 16)),
|
|
402
399
|
}));
|
|
403
400
|
};
|
|
404
401
|
const onUp = () => {
|
|
@@ -410,10 +407,11 @@ function GameWindow(props) {
|
|
|
410
407
|
window.addEventListener("pointerup", onUp);
|
|
411
408
|
}
|
|
412
409
|
const Game = game && game.Game;
|
|
413
|
-
// 最大化:铺满视口(四周留 10px
|
|
410
|
+
// 最大化:铺满视口(四周留 10px);最小化:折叠成标题栏;普通:固定坐标,高度随内容自适应(不裁切);
|
|
411
|
+
// 选择层打开时给固定舒适高度,列表内部滚动(避免内容稀疏导致窗口过矮)。
|
|
414
412
|
const style = win.mode === "max"
|
|
415
413
|
? { left: 10, top: 10, right: 10, bottom: 10, width: "auto", height: "auto" }
|
|
416
|
-
: { left: win.x, top: win.y, width: win.w, height:
|
|
414
|
+
: { left: win.x, top: win.y, width: win.w, height: props.picker ? "min(76vh, 640px)" : "auto" };
|
|
417
415
|
return <div className={"dgwin dgwin-" + win.mode} role="dialog" aria-label={"游戏窗口:" + (game ? game.name : "趣味游戏")} style={style}>
|
|
418
416
|
<header className="dgwin-bar"
|
|
419
417
|
onPointerDown={(e) => beginDrag(e, "move")}
|
|
@@ -522,7 +520,7 @@ function GameFab(props) {
|
|
|
522
520
|
};
|
|
523
521
|
if (!pos) return null;
|
|
524
522
|
return <button type="button"
|
|
525
|
-
className={"dgfab dgfab-" + pos.side + (dragging ? " dgfab-drag" : "") + (props.hidden ? " dgfab-hide" : "")}
|
|
523
|
+
className={"dgfab dgfab-side-" + pos.side + (dragging ? " dgfab-drag" : "") + (props.hidden ? " dgfab-hide" : "")}
|
|
526
524
|
style={{ left: pos.x + "px", top: pos.y + "px" }}
|
|
527
525
|
title="趣味游戏 · 点击开玩;按住可拖到侧栏右缘或屏幕右侧、上下挪位"
|
|
528
526
|
aria-label="趣味游戏快捷入口,可拖动换边与上下挪位"
|
|
@@ -593,9 +591,18 @@ const baseCss = `
|
|
|
593
591
|
.dgfab{--dgame-accent:#a78bfa;position:fixed;z-index:9985;display:flex;flex-direction:column;align-items:center;gap:6px;box-sizing:border-box;width:36px;padding:11px 0;border:1px solid color-mix(in srgb,var(--dgame-accent) 42%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 82%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:var(--dsw-alias-label-primary);font-family:inherit;cursor:grab;user-select:none;touch-action:none;box-shadow:0 6px 24px rgb(0 0 0 / .18);transition:left .22s ease,top .22s ease,border-color .15s ease,opacity .15s ease}.dgfab:hover{border-color:var(--dgame-accent)}.dgfab-side-left{border-radius:0 12px 12px 0;border-left:none}.dgfab-side-right{border-radius:12px 0 0 12px;border-right:none}.dgfab-drag{transition:none;cursor:grabbing;box-shadow:0 10px 34px rgb(0 0 0 / .3);z-index:9986}.dgfab-hide{display:none}.dgfab-ico{display:inline-flex;color:var(--dgame-accent)}.dgfab-label{writing-mode:vertical-rl;letter-spacing:.22em;font-size:11px;line-height:1;color:var(--dsw-alias-label-secondary)}
|
|
594
592
|
.dgwin{--dgame-accent:#a78bfa;--dgame-accent-soft:color-mix(in srgb,#a78bfa 16%,transparent);--dsw-alias-label-primary:#e7ecff;--dsw-alias-label-secondary:#aab3e8;--dsw-alias-label-tertiary:#7d86c0;--dsw-alias-border-l1:rgb(255 255 255 / .12);--dsw-alias-border-l2:rgb(255 255 255 / .17);--dsw-alias-bg-layer-1:#0d1230;--dsw-alias-bg-layer-2:#131a3d;--dsw-alias-interactive-bg-hover:rgb(255 255 255 / .08);position:fixed;z-index:9993;display:flex;flex-direction:column;border:1px solid rgb(255 255 255 / .14);border-radius:14px;background:radial-gradient(600px 300px at 80% -10%,color-mix(in srgb,#312e81 30%,transparent),transparent 60%),radial-gradient(500px 340px at -10% 110%,color-mix(in srgb,#0ea5e9 14%,transparent),transparent 55%),linear-gradient(180deg,#0b1024,#12102a 60%,#0a0f22);color:var(--dsw-alias-label-primary);box-shadow:0 24px 72px rgb(0 0 0 / .5);animation:dgwin-in .18s ease-out}.dgwin::before{content:'';position:absolute;inset:0;pointer-events:none;background-image:radial-gradient(1px 1px at 12% 22%,rgb(255 255 255 / .35),transparent),radial-gradient(1px 1px at 34% 68%,rgb(255 255 255 / .26),transparent),radial-gradient(1.5px 1.5px at 58% 14%,rgb(199 210 254 / .35),transparent),radial-gradient(1px 1px at 76% 44%,rgb(255 255 255 / .22),transparent),radial-gradient(1.5px 1.5px at 88% 78%,rgb(165 243 252 / .3),transparent),radial-gradient(1px 1px at 22% 88%,rgb(255 255 255 / .18),transparent),radial-gradient(1px 1px at 45% 38%,rgb(255 255 255 / .15),transparent),radial-gradient(1px 1px at 67% 86%,rgb(255 255 255 / .19),transparent)}@keyframes dgwin-in{from{opacity:0;transform:translateY(8px) scale(.985)}}
|
|
595
593
|
.dgwin-min{height:auto!important;border-radius:12px}.dgwin-min .dgwin-body{display:none}.dgwin-min .dgwin-resize{display:none}
|
|
596
|
-
.dgwin-max{border-radius:12px}.dgwin-max .dgwin-body .dgame-
|
|
594
|
+
.dgwin-max{border-radius:12px}.dgwin-max .dgwin-body .dgame-game{align-items:stretch}.dgwin-max .dgwin-body .dgame-dash-stage,.dgwin-max .dgwin-body .dgame-tetris,.dgwin-max .dgwin-body .dgame-tetris-board,.dgwin-max .dgwin-body .dgame-snake-stage,.dgwin-max .dgwin-body .dgame-sokoban-stage,.dgwin-max .dgwin-body .dgame-gomoku-board,.dgwin-max .dgwin-body .dgame-xiangqi-board,.dgwin-max .dgwin-body .dgame-tank-stage,.dgwin-max .dgwin-body .dgame-breakout,.dgwin-max .dgwin-body .dgame-racer,.dgwin-max .dgwin-body .dgame-reactor{width:100%;max-width:none}.dgwin-max .dgwin-body .dgame-controls{width:100%;max-width:none}
|
|
597
595
|
.dgwin-bar{flex:none;display:flex;align-items:center;gap:9px;padding:10px 12px;border-bottom:1px solid rgb(255 255 255 / .09);background:rgb(7 11 28 / .72);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);color:#cdd6ff;cursor:move;user-select:none;touch-action:none;position:relative;z-index:2}.dgwin-ico{display:inline-flex;color:#a78bfa}.dgwin-title{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1}.dgwin-title b{font-size:13px}.dgwin-title span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:#8f98ce}.dgwin-task{display:inline-flex;align-items:center;gap:7px;padding:5px 11px;border-radius:999px;border:1px solid rgb(255 255 255 / .13);background:rgb(255 255 255 / .05);font-size:11px;color:#aab3e8;white-space:nowrap}.dgwin-task i{width:7px;height:7px;border-radius:50%;background:#6b7499;flex:none}.dgwin-task.live{color:#c4f5e4;border-color:color-mix(in srgb,#34d399 33%,transparent);background:color-mix(in srgb,#34d399 8%,transparent)}.dgwin-task.live i{background:#34d399;box-shadow:0 0 0 3px color-mix(in srgb,#34d399 14%,transparent);animation:dgame-pulse 1.8s ease-in-out infinite}.dgwin-ctrls{display:flex;align-items:center;gap:6px;flex:none}.dgwin-btn{cursor:pointer;border:1px solid rgb(255 255 255 / .17);background:rgb(255 255 255 / .07);color:#e6ebff;border-radius:8px;padding:5px 10px;font:inherit;font-size:11px;transition:background .15s ease,border-color .15s ease}.dgwin-btn:hover{background:rgb(255 255 255 / .13);border-color:rgb(255 255 255 / .28)}.dgwin-sq{cursor:pointer;border:1px solid transparent;background:transparent;color:#a9b2e2;border-radius:8px;width:28px;height:26px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;transition:background .15s ease,color .15s ease}.dgwin-sq:hover{background:rgb(255 255 255 / .12);color:#fff}
|
|
598
|
-
|
|
596
|
+
/* 小窗内容自适应:高度随内容(不裁切、不滚动),游戏板按屏幕可用高度等比缩放 */
|
|
597
|
+
.dgwin-normal{max-height:calc(100vh - 12px)}
|
|
598
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-snake-stage,.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-tank-stage{width:min(100%,430px,calc(100vh - 240px))}
|
|
599
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-gomoku-board{width:min(100%,390px,calc(100vh - 245px))}
|
|
600
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-xiangqi-board{width:min(100%,370px,calc((100vh - 240px) * .9))}
|
|
601
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-breakout{width:min(100%,410px,calc((100vh - 235px) * .89))}
|
|
602
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-tetris-board{width:min(100%,215px,calc((100vh - 270px) / 2))}
|
|
603
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-dash-stage{width:min(100%,430px,calc((100vh - 215px) * 1.24))}
|
|
604
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-racer{width:min(100%,400px,calc((100vh - 235px) * .71))}
|
|
605
|
+
.dgwin-body{flex:1;min-height:0;position:relative;z-index:1;overflow:hidden;display:flex;flex-direction:column;gap:10px;padding:13px}.dgwin-body .dgame-game{border-color:transparent;background:transparent;padding:0}.dgwin-empty{display:grid;place-items:center;min-height:220px;color:#8f98ce;font-size:12px}.dgwin-tip{flex:none;text-align:center;color:#8b93c9;font-size:11px;padding:0 4px 2px}
|
|
599
606
|
.dgwin-picker{position:absolute;inset:0;z-index:4;display:flex;flex-direction:column;gap:10px;padding:13px;background:rgb(10 14 32 / .86);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);overflow-y:auto;animation:dgentry-in .15s ease-out}.dgwin-picker-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.dgwin-picker-head>div{display:flex;flex-direction:column;gap:3px}.dgwin-picker-head b{font-size:14px}.dgwin-picker-head span{font-size:11px;color:#aab3e8}.dgwin-picker-close{cursor:pointer;border:none;background:transparent;color:#7d86c0;border-radius:8px;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;font-size:14px;flex:none}.dgwin-picker-close:hover{background:rgb(255 255 255 / .08);color:#e7ecff}.dgwin-picker .dgcov-grid{flex-direction:column;--dsw-alias-label-primary:#e7ecff;--dsw-alias-label-secondary:#aab3e8;--dsw-alias-border-l1:rgb(255 255 255 / .12)}.dgwin-picker .dgcov{min-width:0;background:rgb(255 255 255 / .04)}
|
|
600
607
|
.dgwin-resize{position:absolute;right:0;bottom:0;width:18px;height:18px;cursor:nwse-resize;z-index:3;touch-action:none}.dgwin-resize::after{content:'';position:absolute;right:4px;bottom:4px;width:8px;height:8px;border-right:2px solid rgb(255 255 255 / .3);border-bottom:2px solid rgb(255 255 255 / .3);border-radius:0 0 3px 0}
|
|
601
608
|
@keyframes dgentry-in{from{opacity:0}}
|
|
@@ -605,14 +612,14 @@ const baseCss = `
|
|
|
605
612
|
|
|
606
613
|
const dgcovGroupCss = `
|
|
607
614
|
.dgcov-groups{display:flex;flex-direction:column;gap:16px}.dgcov-section{display:flex;flex-direction:column;gap:8px}.dgcov-sechead{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-secondary);font-size:12px}.dgcov-sechead b{font-size:13px;color:var(--dsw-alias-label-primary);font-weight:600}.dgcov-sechead span{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dgcov-sechead em{margin-left:auto;font-style:normal;font-size:10px;color:var(--dgame-accent);border:1px solid color-mix(in srgb,var(--dgame-accent) 40%,transparent);border-radius:999px;padding:1px 8px;white-space:nowrap}
|
|
608
|
-
.dgcov-prev-gomoku{background:linear-gradient(
|
|
609
|
-
.dgcov-prev-xiangqi{
|
|
610
|
-
.dgcov-prev-tetris{background:linear-gradient(180deg,#0c1024,#131a3d)}.
|
|
611
|
-
.dgcov-prev-soko{display:grid;place-items:center;background:linear-gradient(180deg,#181420,#14101f);
|
|
615
|
+
.dgcov-prev-gomoku{background:repeating-linear-gradient(94deg,rgba(124,79,22,.06) 0 2px,transparent 2px 11px),radial-gradient(120% 120% at 30% 10%,#edc97f,#dcae62 55%,#c89a52)}.dgcov-prev-gomoku svg{position:absolute;inset:0;width:100%;height:100%}.dgcov-prev-gomoku line,.dgcov-prev-gomoku rect{stroke:#66431a;stroke-width:3;fill:none}.dgcov-prev-gomoku .dgp-star{fill:#66431a}.dgcov-prev-gomoku .dgp-stone-b{fill:radial-gradient(circle at 34% 30%,#4b5563,#111827 70%);fill:#1f2937;stroke:#0b1220;stroke-width:3}.dgcov-prev-gomoku .dgp-stone-w{fill:#fff;stroke:#94a3b8;stroke-width:3}
|
|
616
|
+
.dgcov-prev-xiangqi{background:repeating-linear-gradient(94deg,rgba(124,79,22,.06) 0 2px,transparent 2px 11px),radial-gradient(120% 120% at 30% 10%,#f0cd86,#ddb067 55%,#c89a52)}.dgcov-prev-xiangqi svg{position:absolute;inset:0;width:100%;height:100%}.dgcov-prev-xiangqi line,.dgcov-prev-xiangqi rect,.dgcov-prev-xiangqi path{stroke:#66431a;stroke-width:3;fill:none}.dgcov-prev-xiangqi .dgp-xq-river{fill:rgba(102,67,26,.65);font-family:KaiTi,"Kaiti SC","STKaiti",serif;font-size:62px;text-anchor:middle;letter-spacing:8px}.dgcov-prev-xiangqi .dgp-xq-pc-b{fill:#f3e0ae;stroke:#8a5a1c;stroke-width:4}.dgcov-prev-xiangqi .dgp-xq-pc-r{fill:#f3e0ae;stroke:#b91c1c;stroke-width:4}.dgcov-prev-xiangqi .dgp-xq-tx-b{fill:#1f2937;font-size:52px;font-weight:700;text-anchor:middle;dominant-baseline:central;font-family:KaiTi,"Kaiti SC","STKaiti",serif}.dgcov-prev-xiangqi .dgp-xq-tx-r{fill:#b91c1c;font-size:52px;font-weight:700;text-anchor:middle;dominant-baseline:central;font-family:KaiTi,"Kaiti SC","STKaiti",serif}
|
|
617
|
+
.dgcov-prev-tetris{display:grid;place-items:center;background:linear-gradient(180deg,#0c1024,#131a3d)}.dgp-tet-grid{display:grid;grid-template-columns:repeat(10,10px);grid-auto-rows:10px;gap:1px;padding:7px;border:1px solid rgba(34,211,238,.3);border-radius:7px;background:rgba(12,16,36,.6)}.dgp-tet-grid i{border-radius:2px;background:rgba(255,255,255,.045);box-shadow:inset 0 0 2px rgba(255,255,255,.07)}.dgp-tet-grid i.cI{background:#22d3ee;box-shadow:0 0 6px rgba(34,211,238,.6)}.dgp-tet-grid i.cO{background:#facc15;box-shadow:0 0 6px rgba(250,204,21,.55)}.dgp-tet-grid i.cT{background:#c084fc;box-shadow:0 0 6px rgba(192,132,252,.55)}.dgp-tet-grid i.cS{background:#4ade80;box-shadow:0 0 6px rgba(74,222,128,.55)}.dgp-tet-grid i.cZ{background:#f87171;box-shadow:0 0 6px rgba(248,113,113,.55)}.dgp-tet-grid i.cJ{background:#60a5fa;box-shadow:0 0 6px rgba(96,165,250,.55)}.dgp-tet-grid i.cL{background:#fb923c;box-shadow:0 0 6px rgba(251,146,60,.55)}.dgp-tet-grid i.fall{animation:dgp-tetbob 1.1s ease-in-out infinite}@keyframes dgp-tetbob{0%,100%{transform:translateY(0)}50%{transform:translateY(4px)}}
|
|
618
|
+
.dgcov-prev-soko{display:grid;place-items:center;background:linear-gradient(180deg,#181420,#14101f)}.dgp-soko-grid{display:grid;grid-template-columns:repeat(7,17px);grid-auto-rows:17px;gap:2px}.dgp-soko-grid i{display:block;border-radius:3px;position:relative}.dgp-soko-floor{background:rgba(255,255,255,.04)}.dgp-soko-wall{background:linear-gradient(180deg,#3a3350,#241f3a);box-shadow:inset 0 1px 0 rgba(255,255,255,.12)}.dgp-soko-box{background:linear-gradient(135deg,#d4a24a,#8a5a1c);box-shadow:inset 0 1px 0 rgba(255,255,255,.4)}.dgp-soko-boxT{background:linear-gradient(135deg,#34d399,#0f766e)}.dgp-soko-target{background:rgba(255,255,255,.04)}.dgp-soko-target::after{content:'';position:absolute;inset:32%;border-radius:50%;background:#f59e0b}.dgp-soko-player{background:radial-gradient(circle at 35% 30%,#e0f2fe,#38bdf8 70%);border-radius:50%!important}
|
|
612
619
|
.dgcov-prev-snake{background:linear-gradient(180deg,#0a1420,#123320)}.dgcov-prev-snake i{position:absolute;width:12px;height:12px;border-radius:26%;background:linear-gradient(135deg,#4ade80,#16a34a);transform:translate(-50%,-50%)}.dgcov-prev-snake i:first-child{background:linear-gradient(135deg,#bbf7d0,#22c55e)}.dgcov-prev-snake i:nth-child(2){background:linear-gradient(135deg,#34d399,#0d9f6e)}.dgcov-prev-snake b{position:absolute;width:13px;height:13px;border-radius:50%;background:#ef4444;box-shadow:0 0 8px #ef4444;transform:translate(-50%,-50%);animation:dgcov-snakefood 1s ease-in-out infinite}@keyframes dgcov-snakefood{50%{transform:translate(-50%,-50%) scale(1.3)}}
|
|
613
620
|
.dgcov-prev-breakout{background:linear-gradient(180deg,#0b1024,#14203a)}.dgcov-prev-breakout i{position:absolute;width:12px;height:5px;border-radius:3px}.dgcov-prev-breakout b{position:absolute;left:50%;bottom:10px;width:34px;height:8px;border-radius:4px;background:#a5f3fc;transform:translateX(-50%)}
|
|
614
621
|
.dgcov-prev-racer{background:linear-gradient(180deg,#0b1024,#1a0f2a)}.dgcov-prev-racer i{position:absolute;width:14px;height:22px;border-radius:5px;transform:translate(-50%,-50%);background:#f87171}.dgcov-prev-racer i:nth-child(even){background:#fb923c}.dgcov-prev-racer b{position:absolute;left:50%;bottom:8px;width:18px;height:30px;border-radius:6px;background:#22d3ee;transform:translateX(-50%)}
|
|
615
|
-
.dgcov-prev-tank{display:grid;place-items:center;
|
|
622
|
+
.dgcov-prev-tank{display:grid;place-items:center;background:linear-gradient(180deg,#1a1425,#100d1c)}.dgp-tank-grid{display:grid;grid-template-columns:repeat(8,18px);grid-auto-rows:18px;gap:2px}.dgp-tank-grid i{display:block;border-radius:3px}.dgp-tank-void{background:rgba(255,255,255,.035)}.dgp-tank-brick{background:repeating-linear-gradient(45deg,#a3672a 0 55%,#8a5a1c 55% 100%);box-shadow:inset 0 0 0 1px rgba(0,0,0,.25)}.dgp-tank-steel{background:linear-gradient(180deg,#94a3b8,#64748b)}.dgp-tank-pw{background:linear-gradient(135deg,#fde047,#eab308);box-shadow:0 0 7px rgba(234,179,8,.55);border-radius:5px}.dgp-tank-en{background:#f87171;box-shadow:0 0 5px rgba(248,113,113,.4);border-radius:5px}.dgp-tank-base{background:#fbbf24;border-radius:50%}
|
|
616
623
|
|
|
617
624
|
`;
|
|
618
625
|
|
|
@@ -18,15 +18,23 @@ export function initialBoard() {
|
|
|
18
18
|
const back = ["k", "h", "e", "a", "g", "a", "e", "h", "k"];
|
|
19
19
|
back.forEach((t, c) => { b[0][c] = { t, c: "b" }; });
|
|
20
20
|
b[2][1] = { t: "c", c: "b" }; b[2][7] = { t: "c", c: "b" };
|
|
21
|
-
|
|
21
|
+
// 黑卒在己方第三横线(row 3,紧邻楚河汉界一侧留一行间隔)。
|
|
22
|
+
[0, 2, 4, 6, 8].forEach((c) => { b[3][c] = { t: "p", c: "b" }; });
|
|
22
23
|
back.forEach((t, c) => { b[9][c] = { t, c: "r" }; });
|
|
23
|
-
b[
|
|
24
|
+
b[7][1] = { t: "c", c: "r" }; b[7][7] = { t: "c", c: "r" };
|
|
25
|
+
// 红兵与红炮和黑方对称(row 6 / row 7)。
|
|
24
26
|
[0, 2, 4, 6, 8].forEach((c) => { b[6][c] = { t: "p", c: "r" }; });
|
|
25
27
|
return b;
|
|
26
28
|
}
|
|
27
29
|
export function cloneBoard(b) {
|
|
28
30
|
return b.map((row) => row.slice());
|
|
29
31
|
}
|
|
32
|
+
// 局面指纹:用于识别重复局面(防止 AI 无限循环将军)。
|
|
33
|
+
export function posKey(board) {
|
|
34
|
+
let s = "";
|
|
35
|
+
for (const row of board) for (const p of row) s += p ? p.t + p.c : ".";
|
|
36
|
+
return s;
|
|
37
|
+
}
|
|
30
38
|
function inBounds(r, c) { return r >= 0 && r < ROWS && c >= 0 && c < COLS; }
|
|
31
39
|
function inPalace(r, c, color) {
|
|
32
40
|
if (c < 3 || c > 5) return false;
|
|
@@ -103,7 +111,7 @@ export function pseudoMoves(board, r, c) {
|
|
|
103
111
|
const push = (nr, nc) => { if (inBounds(nr, nc)) out.push([nr, nc]); };
|
|
104
112
|
const clear = (nr, nc) => !board[nr][nc];
|
|
105
113
|
const enemy = (nr, nc) => { const q = board[nr][nc]; return q && q.c !== p.c; };
|
|
106
|
-
if (p.t === "
|
|
114
|
+
if (p.t === "g") {
|
|
107
115
|
[[-1, 0], [1, 0], [0, -1], [0, 1]].forEach(([dr, dc]) => {
|
|
108
116
|
const nr = r + dr, nc = c + dc;
|
|
109
117
|
if (inPalace(nr, nc, p.c) && (clear(nr, nc) || enemy(nr, nc))) push(nr, nc);
|
|
@@ -245,26 +253,47 @@ function orderMoves(board, moves) {
|
|
|
245
253
|
scored.sort((a, b) => b.s - a.s);
|
|
246
254
|
return scored.map((o) => o.m);
|
|
247
255
|
}
|
|
248
|
-
|
|
256
|
+
// avoid:局面指纹 → 出现次数 的计数表。
|
|
257
|
+
// 优先只在"从未出现过的局面"里选优;若全部重复,给出现次数越多的走法越大的罚分,
|
|
258
|
+
// 避免来回倒子的死循环。根节点对评分相近(±TIE_EPSILON)的走法随机挑一个,
|
|
259
|
+
// 防止每局都走出完全相同的呆板着法。
|
|
260
|
+
export function bestMove(board, color, depth, avoid) {
|
|
249
261
|
const moves = legalMoves(board, color);
|
|
250
262
|
if (!moves.length) return null;
|
|
251
|
-
if (depth <= 1) {
|
|
252
|
-
let best = moves[0], bestScore = -Infinity;
|
|
253
|
-
for (const m of moves) {
|
|
254
|
-
const nb = applyMove(board, m[0], m[1], m[2], m[3]);
|
|
255
|
-
const s = evalFrom(nb, color);
|
|
256
|
-
if (s > bestScore) { bestScore = s; best = m; }
|
|
257
|
-
}
|
|
258
|
-
return best;
|
|
259
|
-
}
|
|
260
263
|
const enemy = color === "r" ? "b" : "r";
|
|
261
264
|
const ordered = orderMoves(board, moves);
|
|
262
|
-
let
|
|
263
|
-
|
|
265
|
+
let pool;
|
|
266
|
+
if (avoid && avoid.size) {
|
|
267
|
+
const seenCount = (m) => avoid.get(posKey(applyMove(board, m[0], m[1], m[2], m[3]))) || 0;
|
|
268
|
+
const minSeen = Math.min(...ordered.map(seenCount));
|
|
269
|
+
pool = minSeen === 0
|
|
270
|
+
? ordered.filter((m) => seenCount(m) === 0).map((m) => ({ m, pen: 0 }))
|
|
271
|
+
: ordered.map((m) => ({ m, pen: seenCount(m) * 60 })).sort((a, b) => a.pen - b.pen);
|
|
272
|
+
} else {
|
|
273
|
+
pool = ordered.map((m) => ({ m, pen: 0 }));
|
|
274
|
+
}
|
|
275
|
+
if (depth <= 1) {
|
|
276
|
+
return pickAmongBest(pool.map(({ m, pen }) => ({ m, s: evalFrom(applyMove(board, m[0], m[1], m[2], m[3]), color) - pen })));
|
|
277
|
+
}
|
|
278
|
+
let bestScore = -Infinity;
|
|
279
|
+
let alpha = -Infinity;
|
|
280
|
+
const scored = [];
|
|
281
|
+
for (const { m, pen } of pool) {
|
|
264
282
|
const nb = applyMove(board, m[0], m[1], m[2], m[3]);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
283
|
+
// 窗口下界放宽 TIE_EPSILON:近似并列的走法仍能拿到精确评分,供随机挑选。
|
|
284
|
+
const s = -negamax(nb, enemy, depth - 1, -Infinity, -alpha) - pen;
|
|
285
|
+
scored.push({ m, s });
|
|
286
|
+
if (s > bestScore) {
|
|
287
|
+
bestScore = s;
|
|
288
|
+
alpha = Math.max(alpha, bestScore - TIE_EPSILON);
|
|
289
|
+
}
|
|
268
290
|
}
|
|
269
|
-
return
|
|
291
|
+
return pickAmongBest(scored);
|
|
292
|
+
}
|
|
293
|
+
const TIE_EPSILON = 12;
|
|
294
|
+
function pickAmongBest(scored) {
|
|
295
|
+
let bestScore = -Infinity;
|
|
296
|
+
for (const o of scored) if (o.s > bestScore) bestScore = o.s;
|
|
297
|
+
const ties = scored.filter((o) => o.s >= bestScore - TIE_EPSILON);
|
|
298
|
+
return ties[Math.floor(Math.random() * ties.length)].m;
|
|
270
299
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-dock",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "DeepSeek Harness 功能坞插件(dsh-dock):模块化功能中枢——每个功能一个 features/ 模块(可单独提取成包发布,又可经 dockBridge 装回面板)。含用量记录(LLM 调用记账/费用统计)、模型设置、图片理解代理、模型余额、任务动画(8 种氛围动效 + 桌面伙伴 3D 场景、完成通知与提示音、钉钉/飞书推送,开关独立、配置持久化);侧栏入口 + 可拖拽弹窗(最大化/最小化/缩放)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/src/client.jsx
CHANGED
|
@@ -24,12 +24,13 @@ import { feature as fHeartbeat } from "../features/heartbeat/view.js";
|
|
|
24
24
|
import { feature as fTheme } from "../features/theme/view.js";
|
|
25
25
|
import { feature as fBalance } from "../features/balance/view.js";
|
|
26
26
|
import { feature as fAnimation } from "../features/animation/view.jsx";
|
|
27
|
+
import { feature as fGames } from "../features/games/view.jsx";
|
|
27
28
|
|
|
28
29
|
const name = "dsh-dock";
|
|
29
|
-
const DOCK_VERSION = "0.
|
|
30
|
+
const DOCK_VERSION = "0.8.0";
|
|
30
31
|
|
|
31
32
|
// ---- 内置功能注册表:新功能 = features/<id>/ 加模块 + 这里 import 一行 ----
|
|
32
|
-
const BUILTIN_FEATURES = [fTokenlog, fModelconfig, fHeartbeat, fTheme, fBalance, fAnimation];
|
|
33
|
+
const BUILTIN_FEATURES = [fTokenlog, fModelconfig, fHeartbeat, fTheme, fBalance, fAnimation, fGames];
|
|
33
34
|
// 规划占位(路线图):接入后移除并建 features/<id>/ 模块
|
|
34
35
|
const PLANNED_FEATURES = [];
|
|
35
36
|
const PLANNED_NOTES = {};
|