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/client.js
CHANGED
|
@@ -41,7 +41,7 @@ __export(client_exports, {
|
|
|
41
41
|
inject: () => inject
|
|
42
42
|
});
|
|
43
43
|
module.exports = __toCommonJS(client_exports);
|
|
44
|
-
var
|
|
44
|
+
var import_react19 = __toESM(require("react"), 1);
|
|
45
45
|
|
|
46
46
|
// src/shared.js
|
|
47
47
|
var import_react = __toESM(require("react"), 1);
|
|
@@ -2330,16 +2330,16 @@ function composerDockPoint(side = "right") {
|
|
|
2330
2330
|
'[contenteditable="true"][data-slot*="conversation.input"]'
|
|
2331
2331
|
];
|
|
2332
2332
|
const seen = /* @__PURE__ */ new Set();
|
|
2333
|
-
const
|
|
2333
|
+
const candidates2 = [];
|
|
2334
2334
|
for (const selector of selectors) {
|
|
2335
2335
|
for (const el of document.querySelectorAll(selector)) {
|
|
2336
2336
|
if (seen.has(el)) continue;
|
|
2337
2337
|
seen.add(el);
|
|
2338
2338
|
const r2 = el.getBoundingClientRect();
|
|
2339
|
-
if (r2.width > 80 && r2.height > 16 && r2.bottom > window.innerHeight * 0.48)
|
|
2339
|
+
if (r2.width > 80 && r2.height > 16 && r2.bottom > window.innerHeight * 0.48) candidates2.push({ el, r: r2 });
|
|
2340
2340
|
}
|
|
2341
2341
|
}
|
|
2342
|
-
const best =
|
|
2342
|
+
const best = candidates2.sort((a, b) => b.r.bottom - a.r.bottom)[0];
|
|
2343
2343
|
if (!best) return fallbackComposerDockPoint(side);
|
|
2344
2344
|
const r = best.r;
|
|
2345
2345
|
const x = side === "ready" ? Math.max(r.left + 16, r.right - 210) : fallbackComposerDockPoint(side).x;
|
|
@@ -3920,9 +3920,3462 @@ var feature6 = {
|
|
|
3920
3920
|
Overlay: AnimationOverlay
|
|
3921
3921
|
};
|
|
3922
3922
|
|
|
3923
|
+
// features/games/view.jsx
|
|
3924
|
+
var import_react18 = require("react");
|
|
3925
|
+
|
|
3926
|
+
// features/games/games/shared.jsx
|
|
3927
|
+
var import_react9 = require("react");
|
|
3928
|
+
function isTypingTarget(event) {
|
|
3929
|
+
const tag = event.target && event.target.tagName || "";
|
|
3930
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
3931
|
+
if (event.target && event.target.isContentEditable) return true;
|
|
3932
|
+
return false;
|
|
3933
|
+
}
|
|
3934
|
+
function useGameControls(stageRef, paused, onKeyDown, onKeyUp) {
|
|
3935
|
+
const pausedRef = (0, import_react9.useRef)(!!paused);
|
|
3936
|
+
pausedRef.current = !!paused;
|
|
3937
|
+
(0, import_react9.useEffect)(() => {
|
|
3938
|
+
if (pausedRef.current) return;
|
|
3939
|
+
const el = stageRef && stageRef.current;
|
|
3940
|
+
if (!el) return;
|
|
3941
|
+
const t = setTimeout(() => {
|
|
3942
|
+
try {
|
|
3943
|
+
el.focus({ preventScroll: true });
|
|
3944
|
+
} catch {
|
|
3945
|
+
}
|
|
3946
|
+
}, 60);
|
|
3947
|
+
return () => clearTimeout(t);
|
|
3948
|
+
}, [paused]);
|
|
3949
|
+
(0, import_react9.useEffect)(() => {
|
|
3950
|
+
const inStage = (event) => {
|
|
3951
|
+
const el = stageRef && stageRef.current;
|
|
3952
|
+
return !!el && (el === event.target || el.contains(event.target));
|
|
3953
|
+
};
|
|
3954
|
+
const handleDown = (event) => {
|
|
3955
|
+
if (pausedRef.current) return;
|
|
3956
|
+
if (isTypingTarget(event)) return;
|
|
3957
|
+
if (inStage(event)) return;
|
|
3958
|
+
if (typeof onKeyDown === "function") onKeyDown(event);
|
|
3959
|
+
};
|
|
3960
|
+
const handleUp = (event) => {
|
|
3961
|
+
if (pausedRef.current) return;
|
|
3962
|
+
if (isTypingTarget(event)) return;
|
|
3963
|
+
if (inStage(event)) return;
|
|
3964
|
+
if (typeof onKeyUp === "function") onKeyUp(event);
|
|
3965
|
+
};
|
|
3966
|
+
window.addEventListener("keydown", handleDown);
|
|
3967
|
+
window.addEventListener("keyup", handleUp);
|
|
3968
|
+
return () => {
|
|
3969
|
+
window.removeEventListener("keydown", handleDown);
|
|
3970
|
+
window.removeEventListener("keyup", handleUp);
|
|
3971
|
+
};
|
|
3972
|
+
}, [onKeyDown, onKeyUp]);
|
|
3973
|
+
}
|
|
3974
|
+
function focusStage(stageRef) {
|
|
3975
|
+
if (stageRef && stageRef.current) {
|
|
3976
|
+
try {
|
|
3977
|
+
stageRef.current.focus({ preventScroll: true });
|
|
3978
|
+
} catch {
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
var _ctx = null;
|
|
3983
|
+
function audioCtx() {
|
|
3984
|
+
if (typeof window === "undefined") return null;
|
|
3985
|
+
const AC = window.AudioContext || window.webkitAudioContext;
|
|
3986
|
+
if (!AC) return null;
|
|
3987
|
+
try {
|
|
3988
|
+
if (!_ctx) _ctx = new AC();
|
|
3989
|
+
if (_ctx.state === "suspended") _ctx.resume().catch(() => {
|
|
3990
|
+
});
|
|
3991
|
+
return _ctx;
|
|
3992
|
+
} catch {
|
|
3993
|
+
return null;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
var SFX_DEFS = {
|
|
3997
|
+
move: { f: 240, t: "square", d: 0.035, g: 0.07 },
|
|
3998
|
+
rotate: { f: 340, t: "square", d: 0.05, g: 0.08 },
|
|
3999
|
+
lock: { f: 150, t: "triangle", d: 0.08, g: 0.15 },
|
|
4000
|
+
step: { f: 190, t: "triangle", d: 0.03, g: 0.06 },
|
|
4001
|
+
push: { f: 115, t: "triangle", d: 0.07, g: 0.14 },
|
|
4002
|
+
stone: { f: 520, t: "triangle", d: 0.05, g: 0.13, f2: 430 },
|
|
4003
|
+
capture: { f: 175, t: "square", d: 0.09, g: 0.15, f2: 105 },
|
|
4004
|
+
check: { t: "square", g: 0.12, d: 0.24, seq: [[660, 0], [660, 0.14]] },
|
|
4005
|
+
eat: { f: 620, t: "square", d: 0.06, g: 0.11, f2: 920 },
|
|
4006
|
+
bounce: { f: 420, t: "square", d: 0.03, g: 0.07 },
|
|
4007
|
+
brick: { f: 740, t: "square", d: 0.05, g: 0.1, f2: 500 },
|
|
4008
|
+
clear: { t: "sine", g: 0.15, d: 0.28, seq: [[523, 0], [659, 0.09], [784, 0.18], [1047, 0.27]] },
|
|
4009
|
+
win: { t: "sine", g: 0.14, d: 0.34, seq: [[523, 0], [659, 0.11], [784, 0.22], [1047, 0.33], [784, 0.47], [1047, 0.56]] },
|
|
4010
|
+
over: { t: "sawtooth", g: 0.11, d: 0.38, seq: [[392, 0], [311, 0.13], [233, 0.27], [185, 0.44]] }
|
|
4011
|
+
};
|
|
4012
|
+
function playSfx(name) {
|
|
4013
|
+
const def = SFX_DEFS[name];
|
|
4014
|
+
const ac = def && audioCtx();
|
|
4015
|
+
if (!ac) return;
|
|
4016
|
+
const t0 = ac.currentTime;
|
|
4017
|
+
const events = def.seq || [[def.f, 0]];
|
|
4018
|
+
for (const [freq, dt] of events) {
|
|
4019
|
+
let osc, gain;
|
|
4020
|
+
try {
|
|
4021
|
+
osc = ac.createOscillator();
|
|
4022
|
+
gain = ac.createGain();
|
|
4023
|
+
osc.type = def.t || "square";
|
|
4024
|
+
osc.frequency.setValueAtTime(freq, t0 + dt);
|
|
4025
|
+
if (def.f2) osc.frequency.exponentialRampToValueAtTime(Math.max(30, def.f2), t0 + dt + def.d);
|
|
4026
|
+
gain.gain.setValueAtTime(1e-4, t0 + dt);
|
|
4027
|
+
gain.gain.exponentialRampToValueAtTime(def.g, t0 + dt + 8e-3);
|
|
4028
|
+
gain.gain.exponentialRampToValueAtTime(1e-4, t0 + dt + def.d);
|
|
4029
|
+
osc.connect(gain).connect(ac.destination);
|
|
4030
|
+
osc.start(t0 + dt);
|
|
4031
|
+
osc.stop(t0 + dt + def.d + 0.03);
|
|
4032
|
+
} catch {
|
|
4033
|
+
if (osc) try {
|
|
4034
|
+
osc.stop();
|
|
4035
|
+
} catch {
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
|
|
4041
|
+
// features/games/games/tetris.jsx
|
|
4042
|
+
var import_react10 = require("react");
|
|
4043
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
4044
|
+
var COLS = 10;
|
|
4045
|
+
var ROWS = 20;
|
|
4046
|
+
var SHAPES = [
|
|
4047
|
+
{ color: "#22d3ee", mat: [[1, 1, 1, 1]] },
|
|
4048
|
+
{ color: "#facc15", mat: [[1, 1], [1, 1]] },
|
|
4049
|
+
{ color: "#c084fc", mat: [[0, 1, 0], [1, 1, 1]] },
|
|
4050
|
+
{ color: "#4ade80", mat: [[0, 1, 1], [1, 1, 0]] },
|
|
4051
|
+
{ color: "#f87171", mat: [[1, 1, 0], [0, 1, 1]] },
|
|
4052
|
+
{ color: "#60a5fa", mat: [[1, 0, 0], [1, 1, 1]] },
|
|
4053
|
+
{ color: "#fb923c", mat: [[0, 0, 1], [1, 1, 1]] }
|
|
4054
|
+
];
|
|
4055
|
+
function rotateCW(mat) {
|
|
4056
|
+
return mat[0].map((_, col) => mat.map((row) => row[col]).reverse());
|
|
4057
|
+
}
|
|
4058
|
+
function rotations(shape) {
|
|
4059
|
+
const list = [shape.mat];
|
|
4060
|
+
let cur = shape.mat;
|
|
4061
|
+
for (let i = 0; i < 3; i += 1) {
|
|
4062
|
+
cur = rotateCW(cur);
|
|
4063
|
+
if (!list.some((m) => JSON.stringify(m) === JSON.stringify(cur))) list.push(cur);
|
|
4064
|
+
}
|
|
4065
|
+
return list;
|
|
4066
|
+
}
|
|
4067
|
+
var PIECES = SHAPES.map((s) => ({ color: s.color, rots: rotations(s) }));
|
|
4068
|
+
var TETRIS_GRID = (() => {
|
|
4069
|
+
const lines = [];
|
|
4070
|
+
let k = 0;
|
|
4071
|
+
for (let i = 1; i < 10; i += 1) lines.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: i * 100, y1: 0, x2: i * 100, y2: 2e3 }, k++));
|
|
4072
|
+
for (let j = 1; j < 20; j += 1) lines.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: 0, y1: j * 100, x2: 1e3, y2: j * 100 }, k++));
|
|
4073
|
+
return lines;
|
|
4074
|
+
})();
|
|
4075
|
+
function emptyBoard() {
|
|
4076
|
+
return Array.from({ length: ROWS }, () => Array(COLS).fill(null));
|
|
4077
|
+
}
|
|
4078
|
+
function collides(board, mat, x, y) {
|
|
4079
|
+
for (let r = 0; r < mat.length; r += 1) {
|
|
4080
|
+
for (let c = 0; c < mat[r].length; c += 1) {
|
|
4081
|
+
if (!mat[r][c]) continue;
|
|
4082
|
+
const bx = x + c, by = y + r;
|
|
4083
|
+
if (bx < 0 || bx >= COLS || by >= ROWS || by >= 0 && board[by][bx]) return true;
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
return false;
|
|
4087
|
+
}
|
|
4088
|
+
function merge(board, mat, x, y, color) {
|
|
4089
|
+
const next = board.map((row) => row.slice());
|
|
4090
|
+
for (let r = 0; r < mat.length; r += 1) {
|
|
4091
|
+
for (let c = 0; c < mat[r].length; c += 1) {
|
|
4092
|
+
if (mat[r][c] && y + r >= 0) next[y + r][x + c] = color;
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
return next;
|
|
4096
|
+
}
|
|
4097
|
+
function clearLines(board) {
|
|
4098
|
+
const kept = board.filter((row) => row.some((cell) => !cell));
|
|
4099
|
+
const cleared = ROWS - kept.length;
|
|
4100
|
+
while (kept.length < ROWS) kept.unshift(Array(COLS).fill(null));
|
|
4101
|
+
return { board: kept, cleared };
|
|
4102
|
+
}
|
|
4103
|
+
function seedBag() {
|
|
4104
|
+
const bag = SHAPES.map((_, i) => i);
|
|
4105
|
+
for (let i = bag.length - 1; i > 0; i -= 1) {
|
|
4106
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
4107
|
+
[bag[i], bag[j]] = [bag[j], bag[i]];
|
|
4108
|
+
}
|
|
4109
|
+
return bag;
|
|
4110
|
+
}
|
|
4111
|
+
var PIECE_SEQ = 0;
|
|
4112
|
+
function makePiece(type, x, y) {
|
|
4113
|
+
const p = PIECES[type];
|
|
4114
|
+
return { id: ++PIECE_SEQ, type, rot: 0, x, y: y != null ? y : -1, mat: p.rots[0], color: p.color };
|
|
4115
|
+
}
|
|
4116
|
+
function initial() {
|
|
4117
|
+
const bag = seedBag();
|
|
4118
|
+
const piece = makePiece(bag.pop(), 3);
|
|
4119
|
+
const next = bag.pop();
|
|
4120
|
+
return { board: emptyBoard(), piece, next, bag, score: 0, lines: 0, level: 1, over: false };
|
|
4121
|
+
}
|
|
4122
|
+
function spawnFrom(state) {
|
|
4123
|
+
let bag = state.bag && state.bag.length ? state.bag.slice() : seedBag();
|
|
4124
|
+
const type = state.next != null ? state.next : bag.pop();
|
|
4125
|
+
let next = bag.length ? bag.pop() : null;
|
|
4126
|
+
if (next == null) {
|
|
4127
|
+
bag = seedBag();
|
|
4128
|
+
next = bag.pop();
|
|
4129
|
+
}
|
|
4130
|
+
const piece = makePiece(type, 3);
|
|
4131
|
+
const over = collides(state.board, piece.mat, piece.x, piece.y) || state.over;
|
|
4132
|
+
return Object.assign({}, state, { bag, next, piece, over });
|
|
4133
|
+
}
|
|
4134
|
+
function advance(state) {
|
|
4135
|
+
if (state.over) return state;
|
|
4136
|
+
const p = state.piece;
|
|
4137
|
+
if (!collides(state.board, p.mat, p.x, p.y + 1)) {
|
|
4138
|
+
return Object.assign({}, state, { piece: Object.assign({}, p, { y: p.y + 1 }), score: state.score + 1 });
|
|
4139
|
+
}
|
|
4140
|
+
const merged = merge(state.board, p.mat, p.x, p.y, p.color);
|
|
4141
|
+
playSfx("lock");
|
|
4142
|
+
const cleared = clearLines(merged);
|
|
4143
|
+
if (cleared.cleared) playSfx(cleared.cleared >= 4 ? "win" : "clear");
|
|
4144
|
+
const lines = state.lines + cleared.cleared;
|
|
4145
|
+
const level = Math.floor(lines / 10) + 1;
|
|
4146
|
+
const base = [0, 100, 300, 500, 800][cleared.cleared] || 800;
|
|
4147
|
+
const scored = Object.assign({}, state, {
|
|
4148
|
+
board: cleared.board,
|
|
4149
|
+
lines,
|
|
4150
|
+
level,
|
|
4151
|
+
score: state.score + base * state.level
|
|
4152
|
+
});
|
|
4153
|
+
return spawnFrom(scored);
|
|
4154
|
+
}
|
|
4155
|
+
function hardDrop(state) {
|
|
4156
|
+
if (state.over) return state;
|
|
4157
|
+
let y = state.piece.y, dist = 0;
|
|
4158
|
+
while (!collides(state.board, state.piece.mat, state.piece.x, y + 1)) {
|
|
4159
|
+
y += 1;
|
|
4160
|
+
dist += 1;
|
|
4161
|
+
}
|
|
4162
|
+
const p = Object.assign({}, state.piece, { y });
|
|
4163
|
+
const merged = merge(state.board, p.mat, p.x, p.y, p.color);
|
|
4164
|
+
playSfx("lock");
|
|
4165
|
+
const cleared = clearLines(merged);
|
|
4166
|
+
if (cleared.cleared) playSfx(cleared.cleared >= 4 ? "win" : "clear");
|
|
4167
|
+
const lines = state.lines + cleared.cleared;
|
|
4168
|
+
const level = Math.floor(lines / 10) + 1;
|
|
4169
|
+
const base = [0, 100, 300, 500, 800][cleared.cleared] || 800;
|
|
4170
|
+
const scored = Object.assign({}, state, {
|
|
4171
|
+
board: cleared.board,
|
|
4172
|
+
lines,
|
|
4173
|
+
level,
|
|
4174
|
+
score: state.score + base * state.level + dist * 2
|
|
4175
|
+
});
|
|
4176
|
+
return spawnFrom(scored);
|
|
4177
|
+
}
|
|
4178
|
+
var KEY_ACTIONS = {
|
|
4179
|
+
"ArrowLeft": "left",
|
|
4180
|
+
"a": "left",
|
|
4181
|
+
"A": "left",
|
|
4182
|
+
"ArrowRight": "right",
|
|
4183
|
+
"d": "right",
|
|
4184
|
+
"D": "right",
|
|
4185
|
+
// 下键 = 快速下滑(一格一格连降,松开即停;空格/回车才是直落到底)。
|
|
4186
|
+
"ArrowDown": "down",
|
|
4187
|
+
"s": "down",
|
|
4188
|
+
"S": "down",
|
|
4189
|
+
"ArrowUp": "cw",
|
|
4190
|
+
"x": "cw",
|
|
4191
|
+
"X": "cw",
|
|
4192
|
+
"z": "ccw",
|
|
4193
|
+
"Z": "ccw",
|
|
4194
|
+
" ": "drop",
|
|
4195
|
+
"Enter": "drop"
|
|
4196
|
+
};
|
|
4197
|
+
function TetrisGame(props) {
|
|
4198
|
+
const [state, setState] = (0, import_react10.useState)(initial);
|
|
4199
|
+
const [manualPause, setManualPause] = (0, import_react10.useState)(false);
|
|
4200
|
+
const pausedRef = (0, import_react10.useRef)(!!(props && props.paused));
|
|
4201
|
+
pausedRef.current = !!(props && props.paused) || manualPause;
|
|
4202
|
+
const stageRef = (0, import_react10.useRef)(null);
|
|
4203
|
+
const act = (0, import_react10.useCallback)((action) => setState((s) => {
|
|
4204
|
+
if (pausedRef.current) return s;
|
|
4205
|
+
if (action === "left") {
|
|
4206
|
+
playSfx("move");
|
|
4207
|
+
const x2 = s.piece.x - 1;
|
|
4208
|
+
if (collides(s.board, s.piece.mat, x2, s.piece.y)) return s;
|
|
4209
|
+
return Object.assign({}, s, { piece: Object.assign({}, s.piece, { x: x2 }) });
|
|
4210
|
+
}
|
|
4211
|
+
if (action === "right") {
|
|
4212
|
+
playSfx("move");
|
|
4213
|
+
const x2 = s.piece.x + 1;
|
|
4214
|
+
if (collides(s.board, s.piece.mat, x2, s.piece.y)) return s;
|
|
4215
|
+
return Object.assign({}, s, { piece: Object.assign({}, s.piece, { x: x2 }) });
|
|
4216
|
+
}
|
|
4217
|
+
if (action === "down") return advance(s);
|
|
4218
|
+
if (action === "drop") return hardDrop(s);
|
|
4219
|
+
if (action === "cw" || action === "ccw") {
|
|
4220
|
+
playSfx("rotate");
|
|
4221
|
+
return rotatePiece(s, action === "cw" ? 1 : -1);
|
|
4222
|
+
}
|
|
4223
|
+
return s;
|
|
4224
|
+
}), []);
|
|
4225
|
+
const softRef = (0, import_react10.useRef)(null);
|
|
4226
|
+
const stateRef = (0, import_react10.useRef)(state);
|
|
4227
|
+
stateRef.current = state;
|
|
4228
|
+
const holdIdRef = (0, import_react10.useRef)(null);
|
|
4229
|
+
const stopSoft = (0, import_react10.useCallback)(() => {
|
|
4230
|
+
if (softRef.current) {
|
|
4231
|
+
clearInterval(softRef.current);
|
|
4232
|
+
softRef.current = null;
|
|
4233
|
+
}
|
|
4234
|
+
}, []);
|
|
4235
|
+
(0, import_react10.useEffect)(() => {
|
|
4236
|
+
if (state.over || manualPause || props.paused) stopSoft();
|
|
4237
|
+
return stopSoft;
|
|
4238
|
+
}, [state.over, manualPause, props.paused, stopSoft]);
|
|
4239
|
+
const onKeyDown = (0, import_react10.useCallback)((event) => {
|
|
4240
|
+
const action = KEY_ACTIONS[event.key];
|
|
4241
|
+
if (action) {
|
|
4242
|
+
event.preventDefault();
|
|
4243
|
+
if (action === "down") {
|
|
4244
|
+
const pieceId = stateRef.current.piece && stateRef.current.piece.id;
|
|
4245
|
+
act("down");
|
|
4246
|
+
if (!softRef.current && !pausedRef.current) {
|
|
4247
|
+
holdIdRef.current = pieceId;
|
|
4248
|
+
softRef.current = setInterval(() => {
|
|
4249
|
+
const cur = stateRef.current;
|
|
4250
|
+
if (pausedRef.current || cur.over || !cur.piece || cur.piece.id !== holdIdRef.current) {
|
|
4251
|
+
stopSoft();
|
|
4252
|
+
return;
|
|
4253
|
+
}
|
|
4254
|
+
act("down");
|
|
4255
|
+
}, 45);
|
|
4256
|
+
}
|
|
4257
|
+
return;
|
|
4258
|
+
}
|
|
4259
|
+
act(action);
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
4262
|
+
if (event.key.toLowerCase() === "p") {
|
|
4263
|
+
event.preventDefault();
|
|
4264
|
+
setManualPause((p) => !p);
|
|
4265
|
+
} else if (event.key.toLowerCase() === "r") {
|
|
4266
|
+
event.preventDefault();
|
|
4267
|
+
setState(initial());
|
|
4268
|
+
setManualPause(false);
|
|
4269
|
+
}
|
|
4270
|
+
}, [act, stopSoft]);
|
|
4271
|
+
const onKeyUp = (0, import_react10.useCallback)((event) => {
|
|
4272
|
+
if (KEY_ACTIONS[event.key] === "down") stopSoft();
|
|
4273
|
+
}, [stopSoft]);
|
|
4274
|
+
(0, import_react10.useEffect)(() => {
|
|
4275
|
+
if (state.over) playSfx("over");
|
|
4276
|
+
}, [state.over]);
|
|
4277
|
+
(0, import_react10.useEffect)(() => {
|
|
4278
|
+
if (pausedRef.current || state.over) return;
|
|
4279
|
+
const speed = Math.max(80, 720 - (state.level - 1) * 70);
|
|
4280
|
+
const timer = setInterval(() => act("down"), speed);
|
|
4281
|
+
return () => clearInterval(timer);
|
|
4282
|
+
}, [state.level, state.over, act, props.paused, manualPause]);
|
|
4283
|
+
useGameControls(stageRef, pausedRef.current, onKeyDown, onKeyUp);
|
|
4284
|
+
const display = state.board.map((row) => row.slice());
|
|
4285
|
+
if (!state.over) {
|
|
4286
|
+
const m = state.piece.mat;
|
|
4287
|
+
for (let r = 0; r < m.length; r += 1) {
|
|
4288
|
+
for (let c = 0; c < m[r].length; c += 1) {
|
|
4289
|
+
if (m[r][c]) {
|
|
4290
|
+
const by = state.piece.y + r, bx = state.piece.x + c;
|
|
4291
|
+
if (by >= 0 && by < ROWS && bx >= 0 && bx < COLS) display[by][bx] = state.piece.color;
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
const nextType = state.next != null ? state.next : 0;
|
|
4297
|
+
const nextMat = PIECES[nextType].rots[0];
|
|
4298
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "dgame-game", "aria-label": "\u4FC4\u7F57\u65AF\u65B9\u5757", children: [
|
|
4299
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-game-head", children: [
|
|
4300
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
4301
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: "\u4FC4\u7F57\u65AF\u65B9\u5757" }),
|
|
4302
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: "\u62FC\u6EE1\u4E00\u884C\u5373\u6D88\u9664\uFF0C\u901F\u5EA6\u968F\u7B49\u7EA7\u52A0\u5FEB\uFF1B\u65B9\u5757\u5806\u5230\u9876\u7AEF\u5C31\u7ED3\u675F\u3002" })
|
|
4303
|
+
] }),
|
|
4304
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-score", children: [
|
|
4305
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
|
|
4306
|
+
"\u5F97\u5206 ",
|
|
4307
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: state.score })
|
|
4308
|
+
] }),
|
|
4309
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
|
|
4310
|
+
"\u7B49\u7EA7 ",
|
|
4311
|
+
state.level,
|
|
4312
|
+
" \xB7 \u6D88\u884C ",
|
|
4313
|
+
state.lines
|
|
4314
|
+
] })
|
|
4315
|
+
] })
|
|
4316
|
+
] }),
|
|
4317
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-tetris", children: [
|
|
4318
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-tetris-board", ref: stageRef, tabIndex: 0, onKeyDown, onKeyUp, onBlur: stopSoft, onClick: () => focusStage(stageRef), "aria-label": "\u4FC4\u7F57\u65AF\u65B9\u5757\u6E38\u620F\u533A\u57DF\uFF0C\u4F7F\u7528\u65B9\u5411\u952E\u4E0E\u7A7A\u683C\u63A7\u5236", children: [
|
|
4319
|
+
display.map((row, rr) => row.map((cell, cc) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("i", { className: "dgame-tetris-cell", style: cell ? { background: cell, boxShadow: "0 0 8px " + cell + "aa, inset 0 0 4px rgba(255,255,255,.5)" } : void 0 }, rr + "-" + cc))),
|
|
4320
|
+
state.over ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-over", children: [
|
|
4321
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "\u65B9\u5757\u5806\u5230\u9876\u4E86" }),
|
|
4322
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => setState(initial()), children: "\u518D\u6765\u4E00\u5C40" })
|
|
4323
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "dgame-tetris-lines", viewBox: "0 0 1000 2000", preserveAspectRatio: "none", "aria-hidden": "true", children: TETRIS_GRID }),
|
|
4324
|
+
!state.over && (props.paused || manualPause) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-over", children: [
|
|
4325
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "\u5DF2\u6682\u505C" }),
|
|
4326
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: () => setManualPause(false), children: "\u7EE7\u7EED\uFF08P\uFF09" })
|
|
4327
|
+
] }) : null
|
|
4328
|
+
] }),
|
|
4329
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-tetris-side", children: [
|
|
4330
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-tetris-next", children: [
|
|
4331
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "\u4E0B\u4E00\u4E2A" }),
|
|
4332
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dgame-tetris-nextbox", children: nextMat.map((row, r) => row.map((cell, c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("i", { className: "dgame-tetris-cell", style: cell ? { background: PIECES[nextType].color } : void 0 }, r + "-" + c))) })
|
|
4333
|
+
] }),
|
|
4334
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-tetris-keys", children: [
|
|
4335
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("b", { children: "\u64CD\u4F5C" }),
|
|
4336
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "\u2190/\u2192 \u79FB\u52A8 \xB7 \u2193 \u5FEB\u901F\u4E0B\u6ED1 \xB7 \u2191/X \u65CB\u8F6C \xB7 Z \u53CD\u5411 \xB7 \u7A7A\u683C \u76F4\u843D \xB7 P \u6682\u505C \xB7 R \u91CD\u5F00" })
|
|
4337
|
+
] })
|
|
4338
|
+
] })
|
|
4339
|
+
] }),
|
|
4340
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dgame-controls", children: [
|
|
4341
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "aria-label": "\u5DE6\u79FB", onClick: () => act("left"), children: "\u2190 \u5DE6\u79FB" }),
|
|
4342
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "\u65B9\u5411\u952E / X\u3001Z\u3001\u7A7A\u683C\u3001P" }),
|
|
4343
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { type: "button", "aria-label": "\u53F3\u79FB", onClick: () => act("right"), children: [
|
|
4344
|
+
"\u53F3\u79FB ",
|
|
4345
|
+
"->"
|
|
4346
|
+
] })
|
|
4347
|
+
] })
|
|
4348
|
+
] });
|
|
4349
|
+
}
|
|
4350
|
+
function rotatePiece(state, dir) {
|
|
4351
|
+
const rots = PIECES[state.piece.type].rots;
|
|
4352
|
+
const len = rots.length;
|
|
4353
|
+
const nextRot = ((state.piece.rot + dir) % len + len) % len;
|
|
4354
|
+
const mat = rots[nextRot];
|
|
4355
|
+
const kicks = [0, -1, 1, -2, 2, -1 - (state.piece.y < 0 ? 1 : 0), 1 + (state.piece.y < 0 ? 1 : 0), -1, 1];
|
|
4356
|
+
for (const kx of kicks) {
|
|
4357
|
+
if (!collides(state.board, mat, state.piece.x + kx, state.piece.y)) {
|
|
4358
|
+
return Object.assign({}, state, { piece: Object.assign({}, state.piece, { rot: nextRot, x: state.piece.x + kx, mat }) });
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
return state;
|
|
4362
|
+
}
|
|
4363
|
+
function TetrisPreview() {
|
|
4364
|
+
const COLORS2 = { I: "I", O: "O", T: "T", S: "S", Z: "Z", J: "J", L: "L" };
|
|
4365
|
+
const ROWS3 = [
|
|
4366
|
+
"..........",
|
|
4367
|
+
"....TT....",
|
|
4368
|
+
"..........",
|
|
4369
|
+
".......Z..",
|
|
4370
|
+
"..OO.S.Z..",
|
|
4371
|
+
"SS..LSOZZ.",
|
|
4372
|
+
"JJSLLSOZZI",
|
|
4373
|
+
"JJSLLOSOII"
|
|
4374
|
+
];
|
|
4375
|
+
const isFall = (r) => r === 1;
|
|
4376
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dgcov-prev dgcov-prev-tetris", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "dgp-tet-grid", children: ROWS3.flatMap((row, r) => row.split("").map((ch, c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("i", { className: COLORS2[ch] ? "c" + COLORS2[ch] + (isFall(r) ? " fall" : "") : "" }, r + "-" + c))) }) });
|
|
4377
|
+
}
|
|
4378
|
+
var tetrisGame = { Game: TetrisGame, Preview: TetrisPreview, css: `
|
|
4379
|
+
.dgame-tetris{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-start;align-self:center;width:fit-content;max-width:100%;margin-inline:auto;justify-content:center}.dgame-tetris-board{position:relative;flex:none;display:grid;grid-template-columns:repeat(10,1fr);grid-template-rows:repeat(20,1fr);width:min(100%,188px);aspect-ratio:10/20;border:1px solid color-mix(in srgb,rgb(34 211 238) 35%,var(--dsw-alias-border-l1));border-radius:8px;overflow:hidden;outline:none;background:linear-gradient(180deg,#0c1024,#131a3d);padding:0;box-shadow:inset 0 0 30px rgb(0 0 0/.45)}.dgame-tetris-board:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,rgb(34 211 238) 40%,transparent)}.dgame-tetris-cell{display:block;background:transparent;border-radius:2px;margin:1px}.dgame-tetris-nextbox{display:grid;grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(2,1fr);width:60px;height:32px}.dgame-tetris-side{flex:1 1 96px;display:flex;flex-direction:column;gap:9px;min-width:0;max-width:220px;color:var(--dsw-alias-label-tertiary);font-size:11px}.dgame-tetris-side b{color:var(--dsw-alias-label-secondary);font-size:12px}.dgame-tetris-side span{font-size:11px}.dgame-tetris-nextbox .dgame-tetris-cell{margin:1px}.dgame-tetris-keys span{display:block;line-height:1.6;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}.dgame-tetris-next{display:flex;flex-direction:column;gap:5px;align-items:center}.dgame-tetris-next span{color:var(--dsw-alias-label-secondary)}.dgame-tetris-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}.dgame-tetris-lines line{stroke:#60a5fa;stroke-opacity:.12;stroke-width:2}@media (max-width:680px){.dgame-tetris{width:100%;gap:8px}.dgame-tetris-board{width:min(100%,170px)}}
|
|
4380
|
+
` };
|
|
4381
|
+
|
|
4382
|
+
// features/games/games/sokoban.jsx
|
|
4383
|
+
var import_react11 = require("react");
|
|
4384
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
4385
|
+
var LEVELS = [
|
|
4386
|
+
["######", "# #", "#@$ .#", "# #", "######"],
|
|
4387
|
+
["######", "# @ #", "# $ #", "# . #", "######"],
|
|
4388
|
+
["#######", "# @ #", "# $$ #", "# .. #", "#######"],
|
|
4389
|
+
["######", "#@ #", "# $ #", "# . #", "# #", "######"],
|
|
4390
|
+
["#######", "# @ #", "# $ #", "# $ .#", "# . #", "#######"],
|
|
4391
|
+
["########", "# #", "# $ $ #", "# . . #", "# @ #", "########"],
|
|
4392
|
+
// —— 扩充关:由易到难(已用求解器验证全部可解)——
|
|
4393
|
+
["########", "# ## #", "#@ $ . #", "# ## #", "########"],
|
|
4394
|
+
["########", "# @ #", "# $$$ #", "# ... #", "# #", "########"],
|
|
4395
|
+
["########", "#@ #", "# $ $ #", "# # #", "# . #. #", "########"],
|
|
4396
|
+
["########", "# @ #", "# $$ #", "# ..# #", "# #", "########"],
|
|
4397
|
+
["########", "#@ #", "# $ $ #", "# #", "# .# . #", "########"],
|
|
4398
|
+
["#########", "# #", "# $ $ $ #", "# ... #", "# @ #", "# #", "#########"],
|
|
4399
|
+
["#########", "# @ #", "# $#$$ #", "# ... #", "# ## #", "#########"],
|
|
4400
|
+
["#########", "#@ # #", "# $ $ #", "# ### #", "# . . #", "#########"],
|
|
4401
|
+
["#########", "# ###", "# $$ # #", "# .. #@ #", "# $$ # #", "# .. #", "#########"]
|
|
4402
|
+
];
|
|
4403
|
+
function cloneGrid(grid) {
|
|
4404
|
+
return grid.map((row) => row.slice());
|
|
4405
|
+
}
|
|
4406
|
+
function parse(rows) {
|
|
4407
|
+
return rows.map((row) => row.split(""));
|
|
4408
|
+
}
|
|
4409
|
+
function findPlayer(grid) {
|
|
4410
|
+
for (let r = 0; r < grid.length; r += 1) {
|
|
4411
|
+
for (let c = 0; c < grid[r].length; c += 1) {
|
|
4412
|
+
if (grid[r][c] === "@" || grid[r][c] === "+") return { r, c };
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
return { r: 0, c: 0 };
|
|
4416
|
+
}
|
|
4417
|
+
function isTarget(ch) {
|
|
4418
|
+
return ch === "." || ch === "*" || ch === "+";
|
|
4419
|
+
}
|
|
4420
|
+
function isBox(ch) {
|
|
4421
|
+
return ch === "$" || ch === "*";
|
|
4422
|
+
}
|
|
4423
|
+
function won(grid) {
|
|
4424
|
+
for (const row of grid) for (const ch of row) if (ch === "$") return false;
|
|
4425
|
+
return true;
|
|
4426
|
+
}
|
|
4427
|
+
function makeInitial(levelIndex) {
|
|
4428
|
+
const grid = parse(LEVELS[levelIndex]);
|
|
4429
|
+
return { grid, moves: 0, pushes: 0, level: levelIndex, wonNew: false, history: [] };
|
|
4430
|
+
}
|
|
4431
|
+
function step(state, dr, dc) {
|
|
4432
|
+
if (state.wonNew) return state;
|
|
4433
|
+
const grid = cloneGrid(state.grid);
|
|
4434
|
+
const p = findPlayer(grid);
|
|
4435
|
+
const nr = p.r + dr, nc = p.c + dc;
|
|
4436
|
+
if (nr < 0 || nr >= grid.length || nc < 0 || nc >= grid[0].length) return state;
|
|
4437
|
+
const target = grid[nr][nc];
|
|
4438
|
+
if (target === "#") return state;
|
|
4439
|
+
grid[p.r][p.c] = isTarget(grid[p.r][p.c]) ? "." : " ";
|
|
4440
|
+
if (isBox(target)) {
|
|
4441
|
+
const br = nr + dr, bc = nc + dc;
|
|
4442
|
+
if (br < 0 || br >= grid.length || bc < 0 || bc >= grid[0].length) return state;
|
|
4443
|
+
const beyond = grid[br][bc];
|
|
4444
|
+
if (beyond === "#" || isBox(beyond)) return state;
|
|
4445
|
+
playSfx("push");
|
|
4446
|
+
grid[br][bc] = isTarget(beyond) ? "*" : "$";
|
|
4447
|
+
grid[nr][nc] = isTarget(target) ? "+" : "@";
|
|
4448
|
+
return Object.assign({}, state, {
|
|
4449
|
+
grid,
|
|
4450
|
+
moves: state.moves + 1,
|
|
4451
|
+
pushes: state.pushes + 1,
|
|
4452
|
+
wonNew: won(grid),
|
|
4453
|
+
history: state.history.concat([{ grid: state.grid, moves: state.moves, pushes: state.pushes }])
|
|
4454
|
+
});
|
|
4455
|
+
}
|
|
4456
|
+
grid[nr][nc] = isTarget(target) ? "+" : "@";
|
|
4457
|
+
playSfx("step");
|
|
4458
|
+
return Object.assign({}, state, {
|
|
4459
|
+
grid,
|
|
4460
|
+
moves: state.moves + 1,
|
|
4461
|
+
wonNew: won(grid),
|
|
4462
|
+
history: state.history.concat([{ grid: state.grid, moves: state.moves, pushes: state.pushes }])
|
|
4463
|
+
});
|
|
4464
|
+
}
|
|
4465
|
+
function undo(state) {
|
|
4466
|
+
if (!state.history.length) return state;
|
|
4467
|
+
const prev = state.history[state.history.length - 1];
|
|
4468
|
+
return Object.assign({}, state, {
|
|
4469
|
+
grid: cloneGrid(prev.grid),
|
|
4470
|
+
moves: prev.moves,
|
|
4471
|
+
pushes: prev.pushes,
|
|
4472
|
+
history: state.history.slice(0, -1),
|
|
4473
|
+
wonNew: false
|
|
4474
|
+
});
|
|
4475
|
+
}
|
|
4476
|
+
var DIRS = { ArrowUp: [-1, 0], ArrowDown: [1, 0], ArrowLeft: [0, -1], ArrowRight: [0, 1], w: [-1, 0], s: [1, 0], a: [0, -1], d: [0, 1], W: [-1, 0], S: [1, 0], A: [0, -1], D: [0, 1] };
|
|
4477
|
+
function SokobanGame(props) {
|
|
4478
|
+
const [level, setLevel] = (0, import_react11.useState)(0);
|
|
4479
|
+
const [state, setState] = (0, import_react11.useState)(() => makeInitial(0));
|
|
4480
|
+
const stageRef = (0, import_react11.useRef)(null);
|
|
4481
|
+
const levelRef = (0, import_react11.useRef)(level);
|
|
4482
|
+
levelRef.current = level;
|
|
4483
|
+
const move = (0, import_react11.useCallback)((dr, dc) => setState((s) => step(s, dr, dc)), []);
|
|
4484
|
+
const prevWon = (0, import_react11.useRef)(false);
|
|
4485
|
+
(0, import_react11.useEffect)(() => {
|
|
4486
|
+
if (state.wonNew && !prevWon.current) playSfx("win");
|
|
4487
|
+
prevWon.current = state.wonNew;
|
|
4488
|
+
}, [state.wonNew]);
|
|
4489
|
+
const nextLevel = (0, import_react11.useCallback)(() => {
|
|
4490
|
+
const n = (levelRef.current + 1) % LEVELS.length;
|
|
4491
|
+
setLevel(n);
|
|
4492
|
+
setState(makeInitial(n));
|
|
4493
|
+
}, []);
|
|
4494
|
+
const pickLevel = (0, import_react11.useCallback)((i) => {
|
|
4495
|
+
setLevel(i);
|
|
4496
|
+
setState(makeInitial(i));
|
|
4497
|
+
}, []);
|
|
4498
|
+
const reset = (0, import_react11.useCallback)(() => setState(makeInitial(levelRef.current)), []);
|
|
4499
|
+
const onKeyDown = (0, import_react11.useCallback)((event) => {
|
|
4500
|
+
const d = DIRS[event.key];
|
|
4501
|
+
if (d) {
|
|
4502
|
+
event.preventDefault();
|
|
4503
|
+
move(d[0], d[1]);
|
|
4504
|
+
return;
|
|
4505
|
+
}
|
|
4506
|
+
if (event.key.toLowerCase() === "u") {
|
|
4507
|
+
event.preventDefault();
|
|
4508
|
+
setState((s) => undo(s));
|
|
4509
|
+
} else if (event.key.toLowerCase() === "r") {
|
|
4510
|
+
event.preventDefault();
|
|
4511
|
+
reset();
|
|
4512
|
+
} else if (event.key === "Enter" && state.wonNew) {
|
|
4513
|
+
event.preventDefault();
|
|
4514
|
+
nextLevel();
|
|
4515
|
+
}
|
|
4516
|
+
}, [move, state.wonNew, reset, nextLevel]);
|
|
4517
|
+
useGameControls(stageRef, props.paused, onKeyDown);
|
|
4518
|
+
const grid = state.grid;
|
|
4519
|
+
const rows = grid.length, cols = grid[0].length;
|
|
4520
|
+
const width = "min(" + cols * 26 + "px, 100%, calc((100vh - 250px) * " + cols + " / " + rows + "))";
|
|
4521
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "dgame-game", "aria-label": "\u63A8\u7BB1\u5B50", children: [
|
|
4522
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dgame-game-head", children: [
|
|
4523
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
4524
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: "\u63A8\u7BB1\u5B50" }),
|
|
4525
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "\u7528\u65B9\u5411\u952E\u628A\u7BB1\u5B50\u63A8\u5230\u76EE\u6807\u683C\u5B50\u4E0A\uFF1B\u5168\u90E8\u5230\u4F4D\u5373\u8FC7\u5173\uFF08U \u64A4\u9500\u3001R \u91CD\u7F6E\uFF09\u3002" })
|
|
4526
|
+
] }),
|
|
4527
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dgame-score", children: [
|
|
4528
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
4529
|
+
"\u6B65\u6570 ",
|
|
4530
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: state.moves })
|
|
4531
|
+
] }),
|
|
4532
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
4533
|
+
"\u63A8\u7BB1 ",
|
|
4534
|
+
state.pushes
|
|
4535
|
+
] })
|
|
4536
|
+
] })
|
|
4537
|
+
] }),
|
|
4538
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dgame-sokoban-levels", role: "tablist", "aria-label": "\u9009\u62E9\u5173\u5361", children: [
|
|
4539
|
+
LEVELS.map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", role: "tab", "aria-selected": i === level, className: i === level ? "on" : "", onClick: () => pickLevel(i), children: i + 1 }) }, i)),
|
|
4540
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => setState((s) => undo(s)), children: "\u64A4\u9500" }),
|
|
4541
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: reset, children: "\u91CD\u7F6E" })
|
|
4542
|
+
] }),
|
|
4543
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dgame-sokoban-stage", ref: stageRef, tabIndex: 0, onKeyDown, onClick: () => focusStage(stageRef), style: { width, gridTemplateColumns: "repeat(" + cols + ",1fr)", gridTemplateRows: "repeat(" + rows + ",1fr)", aspectRatio: cols + " / " + rows }, "aria-label": "\u63A8\u7BB1\u5B50\u6E38\u620F\u533A\u57DF\uFF0C\u4F7F\u7528\u65B9\u5411\u952E\u79FB\u52A8\uFF0CU \u64A4\u9500\uFF0CR \u91CD\u7F6E", children: [
|
|
4544
|
+
grid.map((row, r) => row.map((ch, c) => {
|
|
4545
|
+
let cls = "dgame-soko-cell";
|
|
4546
|
+
if (ch === "#") cls += " wall";
|
|
4547
|
+
else if (ch === "." || ch === "*" || ch === "+") cls += " target";
|
|
4548
|
+
if (ch === "$" || ch === "*") cls += " box";
|
|
4549
|
+
if (ch === "@" || ch === "+") cls += " player";
|
|
4550
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("i", { className: cls, "aria-hidden": "true" }, r + "-" + c);
|
|
4551
|
+
})),
|
|
4552
|
+
state.wonNew ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dgame-over", children: [
|
|
4553
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("strong", { children: [
|
|
4554
|
+
"\u5168\u641E\u5B9A\uFF01\u7B2C ",
|
|
4555
|
+
level + 1,
|
|
4556
|
+
" \u5173\u901A\u8FC7"
|
|
4557
|
+
] }),
|
|
4558
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: nextLevel, children: level + 1 < LEVELS.length ? "\u4E0B\u4E00\u5173" : "\u91CD\u65B0\u5F00\u59CB" })
|
|
4559
|
+
] }) : null
|
|
4560
|
+
] }),
|
|
4561
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dgame-controls", children: [
|
|
4562
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": "\u5411\u4E0A", onClick: () => move(-1, 0), children: "\u2191" }),
|
|
4563
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "\u65B9\u5411\u952E / WASD \xB7 U \u64A4\u9500 \xB7 R \u91CD\u7F6E" }),
|
|
4564
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "aria-label": "\u5411\u4E0B", onClick: () => move(1, 0), children: "\u2193" })
|
|
4565
|
+
] })
|
|
4566
|
+
] });
|
|
4567
|
+
}
|
|
4568
|
+
function SokobanPreview() {
|
|
4569
|
+
const ROWS3 = [
|
|
4570
|
+
"#######",
|
|
4571
|
+
"# #",
|
|
4572
|
+
"# .$@ #",
|
|
4573
|
+
"# .* #",
|
|
4574
|
+
"#######"
|
|
4575
|
+
];
|
|
4576
|
+
const cls = { "#": "wall", " ": "floor", ".": "target", "$": "box", "*": "boxT", "@": "player", "+": "player" };
|
|
4577
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dgcov-prev dgcov-prev-soko", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dgp-soko-grid", children: ROWS3.flatMap((row, r) => row.split("").map((ch, c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("i", { className: "dgp-soko-" + cls[ch] }, r + "-" + c))) }) });
|
|
4578
|
+
}
|
|
4579
|
+
var sokobanGame = { Game: SokobanGame, Preview: SokobanPreview, css: `
|
|
4580
|
+
.dgame-sokoban-stage{position:relative;display:grid;align-self:center;gap:0;padding:6px;border:1px solid color-mix(in srgb,rgb(250 204 21) 32%,var(--dsw-alias-border-l1));border-radius:8px;background:linear-gradient(180deg,#181420,#14101f);outline:none;box-shadow:inset 0 0 26px rgb(0 0 0/.4)}.dgame-sokoban-stage:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,rgb(250 204 21) 30%,transparent)}.dgame-soko-cell{position:relative;display:block;min-width:0;min-height:0}.dgame-soko-cell.wall{background:linear-gradient(180deg,#3a3350,#241f3a);box-shadow:inset 0 1px 0 rgba(255,255,255,.12),inset 0 -1px 0 rgba(0,0,0,.4)}.dgame-soko-cell.target::before{content:'';position:absolute;inset:30%;border-radius:50%;background:radial-gradient(circle at 35% 30%,#fde68a,#f59e0b 70%);box-shadow:0 0 8px color-mix(in srgb,#f59e0b 55%,transparent);opacity:.9}.dgame-soko-cell.box::after{content:'';position:absolute;inset:12%;border-radius:5px;background:linear-gradient(135deg,#d4a24a,#8a5a1c);box-shadow:0 2px 5px rgb(0 0 0/.45),inset 0 1px 0 rgba(255,255,255,.4);border:1px solid color-mix(in srgb,#fde68a 45%,transparent)}.dgame-soko-cell.box.target::after{background:linear-gradient(135deg,#34d399,#0f766e);border-color:#a7f3d0}.dgame-soko-cell.player::before{content:'';position:absolute;inset:18%;border-radius:50%;background:radial-gradient(circle at 35% 30%,#e0f2fe,#38bdf8 70%);box-shadow:0 0 9px color-mix(in srgb,#38bdf8 70%,transparent)}.dgame-sokoban-levels{display:flex;gap:6px;align-items:center;align-self:center;flex-wrap:wrap;width:min(100%,340px);justify-content:center}.dgame-sokoban-levels button{min-width:30px;height:28px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;cursor:pointer;transition:all .15s ease}.dgame-sokoban-levels button.on{color:#fde68a;border-color:color-mix(in srgb,#f59e0b 60%,transparent);background:color-mix(in srgb,#f59e0b 18%,transparent)}.dgame-sokoban-levels button:hover{border-color:var(--dgame-accent);color:var(--dsw-alias-label-primary)}
|
|
4581
|
+
` };
|
|
4582
|
+
|
|
4583
|
+
// features/games/games/gomoku.jsx
|
|
4584
|
+
var import_react12 = require("react");
|
|
4585
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
4586
|
+
var GOMOKU_LINES = (() => {
|
|
4587
|
+
const P = (n) => n * 100 + 50;
|
|
4588
|
+
const parts = [/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: P(0), y: P(0), width: 1400, height: 1400, strokeWidth: 6.5, fill: "none" }, "frame")];
|
|
4589
|
+
let k = 0;
|
|
4590
|
+
for (let i = 1; i <= 13; i += 1) {
|
|
4591
|
+
parts.push(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: P(i), y1: P(0), x2: P(i), y2: P(14) }, k++));
|
|
4592
|
+
parts.push(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: P(0), y1: P(i), x2: P(14), y2: P(i) }, k++));
|
|
4593
|
+
}
|
|
4594
|
+
for (const [r, c] of [[3, 3], [3, 11], [7, 7], [11, 3], [11, 11]]) {
|
|
4595
|
+
parts.push(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { className: "dgame-gomoku-star", cx: P(c), cy: P(r), r: 15 }, k++));
|
|
4596
|
+
}
|
|
4597
|
+
return parts;
|
|
4598
|
+
})();
|
|
4599
|
+
var SIZE = 15;
|
|
4600
|
+
var DIRS2 = [[0, 1], [1, 0], [1, 1], [1, -1]];
|
|
4601
|
+
function emptyBoard2() {
|
|
4602
|
+
return Array.from({ length: SIZE }, () => Array(SIZE).fill(null));
|
|
4603
|
+
}
|
|
4604
|
+
function inBoard(r, c) {
|
|
4605
|
+
return r >= 0 && r < SIZE && c >= 0 && c < SIZE;
|
|
4606
|
+
}
|
|
4607
|
+
function lineScore(board, r, c, color) {
|
|
4608
|
+
let total = 0;
|
|
4609
|
+
for (const [dr, dc] of DIRS2) {
|
|
4610
|
+
let count = 1;
|
|
4611
|
+
let rr = r + dr, cc = c + dc;
|
|
4612
|
+
let openEnds = 0;
|
|
4613
|
+
while (inBoard(rr, cc) && board[rr][cc] === color) {
|
|
4614
|
+
count += 1;
|
|
4615
|
+
rr += dr;
|
|
4616
|
+
cc += dc;
|
|
4617
|
+
}
|
|
4618
|
+
if (inBoard(rr, cc) && board[rr][cc] === null) openEnds += 1;
|
|
4619
|
+
rr = r - dr;
|
|
4620
|
+
cc = c - dc;
|
|
4621
|
+
while (inBoard(rr, cc) && board[rr][cc] === color) {
|
|
4622
|
+
count += 1;
|
|
4623
|
+
rr -= dr;
|
|
4624
|
+
cc -= dc;
|
|
4625
|
+
}
|
|
4626
|
+
if (inBoard(rr, cc) && board[rr][cc] === null) openEnds += 1;
|
|
4627
|
+
total += patternScore(count, openEnds);
|
|
4628
|
+
}
|
|
4629
|
+
return total;
|
|
4630
|
+
}
|
|
4631
|
+
function patternScore(count, openEnds) {
|
|
4632
|
+
if (count >= 5) return 1e7;
|
|
4633
|
+
if (openEnds === 0) return 0;
|
|
4634
|
+
if (count === 4) return openEnds === 2 ? 1e6 : 1e5;
|
|
4635
|
+
if (count === 3) return openEnds === 2 ? 1e4 : 1e3;
|
|
4636
|
+
if (count === 2) return openEnds === 2 ? 200 : 20;
|
|
4637
|
+
if (count === 1) return openEnds === 2 ? 10 : 1;
|
|
4638
|
+
return 0;
|
|
4639
|
+
}
|
|
4640
|
+
function winLine(board, r, c, color) {
|
|
4641
|
+
for (const [dr, dc] of DIRS2) {
|
|
4642
|
+
const cells = [[r, c]];
|
|
4643
|
+
let rr = r + dr, cc = c + dc;
|
|
4644
|
+
while (inBoard(rr, cc) && board[rr][cc] === color) {
|
|
4645
|
+
cells.push([rr, cc]);
|
|
4646
|
+
rr += dr;
|
|
4647
|
+
cc += dc;
|
|
4648
|
+
}
|
|
4649
|
+
rr = r - dr;
|
|
4650
|
+
cc = c - dc;
|
|
4651
|
+
while (inBoard(rr, cc) && board[rr][cc] === color) {
|
|
4652
|
+
cells.push([rr, cc]);
|
|
4653
|
+
rr -= dr;
|
|
4654
|
+
cc -= dc;
|
|
4655
|
+
}
|
|
4656
|
+
if (cells.length >= 5) return cells;
|
|
4657
|
+
}
|
|
4658
|
+
return null;
|
|
4659
|
+
}
|
|
4660
|
+
function boardFull(board) {
|
|
4661
|
+
for (const row of board) for (const cell of row) if (!cell) return false;
|
|
4662
|
+
return true;
|
|
4663
|
+
}
|
|
4664
|
+
function candidates(board) {
|
|
4665
|
+
const set = [];
|
|
4666
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4667
|
+
let hasStone = false;
|
|
4668
|
+
for (let r = 0; r < SIZE; r += 1) {
|
|
4669
|
+
for (let c = 0; c < SIZE; c += 1) {
|
|
4670
|
+
if (!board[r][c]) continue;
|
|
4671
|
+
hasStone = true;
|
|
4672
|
+
for (let dr = -2; dr <= 2; dr += 1) {
|
|
4673
|
+
for (let dc = -2; dc <= 2; dc += 1) {
|
|
4674
|
+
const rr = r + dr, cc = c + dc;
|
|
4675
|
+
if (inBoard(rr, cc) && !board[rr][cc] && !seen.has(rr * SIZE + cc)) {
|
|
4676
|
+
seen.add(rr * SIZE + cc);
|
|
4677
|
+
set.push([rr, cc]);
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
if (!hasStone) return [[7, 7], [7, 8], [8, 7]];
|
|
4684
|
+
return set;
|
|
4685
|
+
}
|
|
4686
|
+
function aiMove(board) {
|
|
4687
|
+
const cands = candidates(board);
|
|
4688
|
+
let best = null, bestScore = -1;
|
|
4689
|
+
for (const [r, c] of cands) {
|
|
4690
|
+
const attack = lineScore(board, r, c, "w");
|
|
4691
|
+
const defend = lineScore(board, r, c, "b");
|
|
4692
|
+
const score = attack + defend * 0.92;
|
|
4693
|
+
if (score > bestScore) {
|
|
4694
|
+
bestScore = score;
|
|
4695
|
+
best = [r, c];
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
return best || [7, 7];
|
|
4699
|
+
}
|
|
4700
|
+
function GomokuGame(props) {
|
|
4701
|
+
const [board, setBoard] = (0, import_react12.useState)(emptyBoard2);
|
|
4702
|
+
const [turn, setTurn] = (0, import_react12.useState)("b");
|
|
4703
|
+
const [winner, setWinner] = (0, import_react12.useState)(null);
|
|
4704
|
+
const [winCells, setWinCells] = (0, import_react12.useState)(null);
|
|
4705
|
+
const [showResult, setShowResult] = (0, import_react12.useState)(false);
|
|
4706
|
+
const [last, setLast] = (0, import_react12.useState)(null);
|
|
4707
|
+
const [history, setHistory] = (0, import_react12.useState)([]);
|
|
4708
|
+
const [thinking, setThinking] = (0, import_react12.useState)(false);
|
|
4709
|
+
const boardRef = (0, import_react12.useRef)(board);
|
|
4710
|
+
const stageRef = (0, import_react12.useRef)(null);
|
|
4711
|
+
boardRef.current = board;
|
|
4712
|
+
const turnRef = (0, import_react12.useRef)(turn);
|
|
4713
|
+
turnRef.current = turn;
|
|
4714
|
+
const winRef = (0, import_react12.useRef)(winner);
|
|
4715
|
+
winRef.current = winner;
|
|
4716
|
+
const finish = (0, import_react12.useCallback)((b, r, c, color) => {
|
|
4717
|
+
const line = winLine(b, r, c, color);
|
|
4718
|
+
if (line) {
|
|
4719
|
+
setWinner(color);
|
|
4720
|
+
setWinCells(line);
|
|
4721
|
+
setShowResult(true);
|
|
4722
|
+
playSfx(color === "b" ? "win" : "over");
|
|
4723
|
+
} else if (boardFull(b)) {
|
|
4724
|
+
setWinner("draw");
|
|
4725
|
+
setWinCells(null);
|
|
4726
|
+
setShowResult(true);
|
|
4727
|
+
playSfx("clear");
|
|
4728
|
+
} else playSfx("stone");
|
|
4729
|
+
}, []);
|
|
4730
|
+
const place = (0, import_react12.useCallback)((r, c, color) => {
|
|
4731
|
+
if (boardRef.current[r][c]) return false;
|
|
4732
|
+
const next = boardRef.current.map((row) => row.slice());
|
|
4733
|
+
next[r][c] = color;
|
|
4734
|
+
setBoard(next);
|
|
4735
|
+
boardRef.current = next;
|
|
4736
|
+
setLast({ r, c });
|
|
4737
|
+
setHistory((h) => h.concat([{ r, c, color }]));
|
|
4738
|
+
finish(next, r, c, color);
|
|
4739
|
+
return true;
|
|
4740
|
+
}, [finish]);
|
|
4741
|
+
(0, import_react12.useEffect)(() => {
|
|
4742
|
+
if (turn !== "w" || winner || props.paused) return;
|
|
4743
|
+
setThinking(true);
|
|
4744
|
+
const t = setTimeout(() => {
|
|
4745
|
+
const [r, c] = aiMove(boardRef.current);
|
|
4746
|
+
place(r, c, "w");
|
|
4747
|
+
setTurn("b");
|
|
4748
|
+
setThinking(false);
|
|
4749
|
+
}, 260);
|
|
4750
|
+
return () => clearTimeout(t);
|
|
4751
|
+
}, [turn, winner, props.paused, place]);
|
|
4752
|
+
const onCell = (0, import_react12.useCallback)((r, c) => {
|
|
4753
|
+
if (winRef.current || turnRef.current !== "b" || thinking || boardRef.current[r][c]) return;
|
|
4754
|
+
place(r, c, "b");
|
|
4755
|
+
setTurn("w");
|
|
4756
|
+
}, [thinking, place]);
|
|
4757
|
+
const reset = (0, import_react12.useCallback)(() => {
|
|
4758
|
+
setBoard(emptyBoard2());
|
|
4759
|
+
setTurn("b");
|
|
4760
|
+
setWinner(null);
|
|
4761
|
+
setWinCells(null);
|
|
4762
|
+
setShowResult(false);
|
|
4763
|
+
setLast(null);
|
|
4764
|
+
setHistory([]);
|
|
4765
|
+
setThinking(false);
|
|
4766
|
+
}, []);
|
|
4767
|
+
const undo2 = (0, import_react12.useCallback)(() => {
|
|
4768
|
+
if (thinking || history.length < 2) return;
|
|
4769
|
+
const h = history.slice(0, -2);
|
|
4770
|
+
const next = emptyBoard2();
|
|
4771
|
+
for (const m of h) next[m.r][m.c] = m.color;
|
|
4772
|
+
setBoard(next);
|
|
4773
|
+
boardRef.current = next;
|
|
4774
|
+
setHistory(h);
|
|
4775
|
+
setTurn("b");
|
|
4776
|
+
setWinner(null);
|
|
4777
|
+
setWinCells(null);
|
|
4778
|
+
setShowResult(false);
|
|
4779
|
+
setLast(h.length ? h[h.length - 1] : null);
|
|
4780
|
+
}, [history, thinking]);
|
|
4781
|
+
const onKeyDown = (0, import_react12.useCallback)((event) => {
|
|
4782
|
+
if (event.key.toLowerCase() === "r") {
|
|
4783
|
+
event.preventDefault();
|
|
4784
|
+
reset();
|
|
4785
|
+
} else if (event.key.toLowerCase() === "u") {
|
|
4786
|
+
event.preventDefault();
|
|
4787
|
+
undo2();
|
|
4788
|
+
}
|
|
4789
|
+
}, [reset, undo2]);
|
|
4790
|
+
useGameControls(stageRef, props.paused, onKeyDown);
|
|
4791
|
+
const statusText = winner === "b" ? "\u4F60\u8D62\u4E86 \xB7 \u4E94\u5B50\u8FDE\u73E0" : winner === "w" ? "AI \u8D62\u4E86 \xB7 \u518D\u8BD5\u4E00\u6B21" : winner === "draw" ? "\u548C\u68CB \xB7 \u68CB\u76D8\u5DF2\u6EE1" : turn === "b" ? "\u8F6E\u5230\u4F60\u843D\u5B50\uFF08\u9ED1\uFF09" : "AI \u601D\u8003\u4E2D\u2026\uFF08\u767D\uFF09";
|
|
4792
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "dgame-game", "aria-label": "\u4E94\u5B50\u68CB", children: [
|
|
4793
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dgame-game-head", children: [
|
|
4794
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
4795
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { children: "\u4E94\u5B50\u68CB" }),
|
|
4796
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "\u4F60\u6267\u9ED1\u5148\u624B\uFF0CAI \u6267\u767D\uFF1B\u5148\u5728\u6A2A\u7AD6\u659C\u4EFB\u4E00\u7EBF\u8FDE\u6210\u4E94\u5B50\u8005\u80DC\u3002" })
|
|
4797
|
+
] }),
|
|
4798
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dgame-score", children: [
|
|
4799
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "\u56DE\u5408" }),
|
|
4800
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { className: "dg-gomoku-status", children: statusText }),
|
|
4801
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: undo2, children: "\u64A4\u9500" }),
|
|
4802
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: reset, children: "\u91CD\u5F00" })
|
|
4803
|
+
] })
|
|
4804
|
+
] }),
|
|
4805
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dgame-gomoku-board", ref: stageRef, tabIndex: 0, onKeyDown, onClick: () => focusStage(stageRef), role: "grid", "aria-label": "\u5341\u4E94\u8DEF\u4E94\u5B50\u68CB\u68CB\u76D8", children: [
|
|
4806
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "dgame-gomoku-lines", viewBox: "0 0 1500 1500", preserveAspectRatio: "none", "aria-hidden": "true", children: GOMOKU_LINES }),
|
|
4807
|
+
board.map((row, r) => row.map((cell, c) => {
|
|
4808
|
+
const isWin = winCells && winCells.some(([wr, wc]) => wr === r && wc === c);
|
|
4809
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
4810
|
+
"button",
|
|
4811
|
+
{
|
|
4812
|
+
type: "button",
|
|
4813
|
+
role: "gridcell",
|
|
4814
|
+
className: "dgame-gomoku-cell" + (cell ? " filled " + cell : "") + (last && last.r === r && last.c === c ? " last" : "") + (isWin ? " wincell" : ""),
|
|
4815
|
+
"aria-label": "\u7B2C" + (r + 1) + "\u884C\u7B2C" + (c + 1) + "\u5217" + (cell === "b" ? " \u9ED1\u5B50" : cell === "w" ? " \u767D\u5B50" : " \u7A7A\u683C"),
|
|
4816
|
+
onClick: () => {
|
|
4817
|
+
focusStage(stageRef);
|
|
4818
|
+
onCell(r, c);
|
|
4819
|
+
},
|
|
4820
|
+
children: cell ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("i", { className: "dgame-gomoku-stone " + (cell === "b" ? "black" : "white") }) : null
|
|
4821
|
+
},
|
|
4822
|
+
r + "-" + c
|
|
4823
|
+
);
|
|
4824
|
+
})),
|
|
4825
|
+
winner && showResult ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dgame-gomoku-result", role: "status", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dgame-gomoku-result-card " + winner, children: [
|
|
4826
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dgame-gomoku-result-icon", "aria-hidden": "true", children: winner === "b" ? "\u{1F389}" : winner === "w" ? "\u{1F916}" : "\u{1F91D}" }),
|
|
4827
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: winner === "b" ? "\u4F60\u8D62\u4E86\uFF01" : winner === "w" ? "AI \u8D62\u4E86" : "\u548C\u68CB" }),
|
|
4828
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: winner === "b" ? "\u4E94\u5B50\u8FDE\u73E0\uFF0C\u6F02\u4EAE\uFF01" : winner === "w" ? "\u518D\u63A5\u518D\u5389\uFF0C\u5377\u571F\u91CD\u6765\u3002" : "\u68CB\u76D8\u5DF2\u6EE1\uFF0C\u5E73\u5206\u79CB\u8272\u3002" }),
|
|
4829
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dgame-gomoku-result-actions", children: [
|
|
4830
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: (e) => {
|
|
4831
|
+
e.stopPropagation();
|
|
4832
|
+
setShowResult(false);
|
|
4833
|
+
}, children: "\u67E5\u770B\u68CB\u76D8" }),
|
|
4834
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: (e) => {
|
|
4835
|
+
e.stopPropagation();
|
|
4836
|
+
reset();
|
|
4837
|
+
}, children: "\u518D\u6765\u4E00\u5C40" })
|
|
4838
|
+
] })
|
|
4839
|
+
] }) }) : null
|
|
4840
|
+
] }),
|
|
4841
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dgame-controls", children: [
|
|
4842
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: undo2, children: "\u64A4\u9500" }),
|
|
4843
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: statusText }),
|
|
4844
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: reset, children: "\u91CD\u5F00" })
|
|
4845
|
+
] })
|
|
4846
|
+
] });
|
|
4847
|
+
}
|
|
4848
|
+
function GomokuPreview() {
|
|
4849
|
+
const P = (n) => n * 100 + 50;
|
|
4850
|
+
const stones = [[3, 3, "b"], [6, 3, "w"], [4, 4, "w"], [5, 5, "b"], [2, 6, "w"], [6, 6, "b"]];
|
|
4851
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dgcov-prev dgcov-prev-gomoku", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { viewBox: "0 0 900 900", preserveAspectRatio: "xMidYMid slice", children: [
|
|
4852
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: P(0), y: P(0), width: 800, height: 800, fill: "none", strokeWidth: 11 }),
|
|
4853
|
+
[1, 2, 3, 4, 5, 6, 7].map((i) => [
|
|
4854
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: P(i), y1: P(0), x2: P(i), y2: P(8) }, "v" + i),
|
|
4855
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: P(0), y1: P(i), x2: P(8), y2: P(i) }, "h" + i)
|
|
4856
|
+
]),
|
|
4857
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { className: "dgp-star", cx: P(4), cy: P(4), r: 16 }),
|
|
4858
|
+
stones.map(([r, c, col], i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { className: "dgp-stone-" + col, cx: P(c), cy: P(r), r: 40 }, i))
|
|
4859
|
+
] }) });
|
|
4860
|
+
}
|
|
4861
|
+
var gomokuGame = { Game: GomokuGame, Preview: GomokuPreview, css: `
|
|
4862
|
+
.dgame-gomoku-board{position:relative;display:grid;grid-template-columns:repeat(15,1fr);grid-template-rows:repeat(15,1fr);align-self:center;width:min(100%,min(90vw,330px));aspect-ratio:1/1;padding:12px;border-radius:12px;border:1px solid color-mix(in srgb,#7c4f16 65%,transparent);box-shadow:0 12px 32px rgb(0 0 0/.5),inset 0 1px 0 rgba(255,236,200,.55),inset 0 -3px 9px rgba(90,50,10,.35),inset 3px 0 8px rgba(255,224,170,.28),inset -3px 0 8px rgba(120,70,20,.25);outline:none;background:repeating-linear-gradient(94deg,rgba(124,79,22,.05) 0 2px,transparent 2px 11px),repeating-linear-gradient(87deg,rgba(255,238,196,.05) 0 2px,transparent 2px 14px),radial-gradient(120% 110% at 30% 10%,#edc97f,#dcae62 52%,#cc9a52 80%,#bb8844)}.dgame-gomoku-lines{position:absolute;inset:12px;pointer-events:none}.dgame-gomoku-lines line{stroke:#66431a;stroke-width:3;fill:none}.dgame-gomoku-star{fill:#66431a}.dgame-gomoku-cell{position:relative;z-index:1;background:transparent;border:none;cursor:pointer;padding:0;margin:0;outline:none;min-width:0;min-height:0}.dgame-gomoku-cell:focus-visible::after{content:'';position:absolute;inset:8%;border:2px solid rgba(255,255,255,.7);border-radius:50%}.dgame-gomoku-stone{position:absolute;inset:10%;border-radius:50%;display:block}.dgame-gomoku-stone.black{background:radial-gradient(circle at 34% 30%,#4b5563,#111827 68%);box-shadow:0 2px 5px rgb(0 0 0/.5),inset 0 1px 1px rgba(255,255,255,.25)}.dgame-gomoku-stone.white{background:radial-gradient(circle at 34% 30%,#ffffff,#cbd5e1 70%);box-shadow:0 2px 5px rgb(0 0 0/.4),inset 0 1px 1px rgba(255,255,255,.9)}.dgame-gomoku-cell.last .dgame-gomoku-stone::after{content:'';position:absolute;inset:32%;border-radius:50%;background:rgba(255,80,80,.9)}.dg-gomoku-status{font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
4863
|
+
.dgame-gomoku-cell.wincell .dgame-gomoku-stone{animation:dg-gomoku-win 1s ease-in-out infinite;z-index:1}
|
|
4864
|
+
@keyframes dg-gomoku-win{0%,100%{box-shadow:0 2px 5px rgb(0 0 0/.5),0 0 0 0 rgba(255,214,102,.85)}50%{box-shadow:0 2px 5px rgb(0 0 0/.5),0 0 0 6px rgba(255,214,102,0)}}
|
|
4865
|
+
.dgame-gomoku-result{position:absolute;inset:-1px;z-index:5;display:grid;place-items:center;background:rgba(10,10,20,.55);backdrop-filter:blur(2px);border-radius:8px;animation:dg-gomoku-fade .25s ease-out}
|
|
4866
|
+
.dgame-gomoku-result-card{display:flex;flex-direction:column;align-items:center;gap:6px;padding:18px 26px;border-radius:14px;background:linear-gradient(160deg,#241f38,#171427);border:1px solid color-mix(in srgb,var(--dgame-accent,#a78bfa) 45%,transparent);box-shadow:0 12px 40px rgb(0 0 0/.55),0 0 0 4px rgba(167,139,250,.08);animation:dg-gomoku-pop .35s cubic-bezier(.22,1.6,.36,1);text-align:center}
|
|
4867
|
+
.dgame-gomoku-result-card strong{font-size:18px;color:#fff}
|
|
4868
|
+
.dgame-gomoku-result-card.b strong{color:#fbbf24}.dgame-gomoku-result-card.w strong{color:#e5e7eb}.dgame-gomoku-result-card.draw strong{color:#cbd5e1}
|
|
4869
|
+
.dgame-gomoku-result-icon{font-size:28px;line-height:1;animation:dg-gomoku-bounce 1.2s ease-in-out infinite}
|
|
4870
|
+
.dgame-gomoku-result-card p{margin:0;font-size:12px;color:var(--dsw-alias-label-tertiary,#9ca3af)}
|
|
4871
|
+
.dgame-gomoku-result-actions{display:flex;gap:10px;margin-top:6px}
|
|
4872
|
+
.dgame-gomoku-result-actions button{padding:6px 14px;border-radius:8px;border:1px solid color-mix(in srgb,var(--dgame-accent,#a78bfa) 40%,transparent);background:rgba(167,139,250,.14);color:#ede9fe;font-size:13px;cursor:pointer}
|
|
4873
|
+
.dgame-gomoku-result-actions button:hover{background:rgba(167,139,250,.28)}
|
|
4874
|
+
@keyframes dg-gomoku-fade{from{opacity:0}to{opacity:1}}
|
|
4875
|
+
@keyframes dg-gomoku-pop{from{opacity:0;transform:scale(.6)}to{opacity:1;transform:scale(1)}}
|
|
4876
|
+
@keyframes dg-gomoku-bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
|
|
4877
|
+
` };
|
|
4878
|
+
|
|
4879
|
+
// features/games/games/xiangqi.jsx
|
|
4880
|
+
var import_react13 = require("react");
|
|
4881
|
+
|
|
4882
|
+
// features/games/xiangqi-core.js
|
|
4883
|
+
var ROWS2 = 10;
|
|
4884
|
+
var COLS2 = 9;
|
|
4885
|
+
function pieceChar(p) {
|
|
4886
|
+
const map = {
|
|
4887
|
+
g: { r: "\u5E25", b: "\u5C07" },
|
|
4888
|
+
a: { r: "\u4ED5", b: "\u58EB" },
|
|
4889
|
+
e: { r: "\u76F8", b: "\u8C61" },
|
|
4890
|
+
h: { r: "\u99AC", b: "\u99AC" },
|
|
4891
|
+
k: { r: "\u8ECA", b: "\u8ECA" },
|
|
4892
|
+
c: { r: "\u70AE", b: "\u70AE" },
|
|
4893
|
+
p: { r: "\u5175", b: "\u5352" }
|
|
4894
|
+
};
|
|
4895
|
+
return map[p.t] && map[p.t][p.c];
|
|
4896
|
+
}
|
|
4897
|
+
var VALUE = { g: 1e5, k: 900, c: 450, h: 400, e: 200, a: 200, p: 100 };
|
|
4898
|
+
function initialBoard() {
|
|
4899
|
+
const b = Array.from({ length: ROWS2 }, () => Array(COLS2).fill(null));
|
|
4900
|
+
const back = ["k", "h", "e", "a", "g", "a", "e", "h", "k"];
|
|
4901
|
+
back.forEach((t, c) => {
|
|
4902
|
+
b[0][c] = { t, c: "b" };
|
|
4903
|
+
});
|
|
4904
|
+
b[2][1] = { t: "c", c: "b" };
|
|
4905
|
+
b[2][7] = { t: "c", c: "b" };
|
|
4906
|
+
[0, 2, 4, 6, 8].forEach((c) => {
|
|
4907
|
+
b[3][c] = { t: "p", c: "b" };
|
|
4908
|
+
});
|
|
4909
|
+
back.forEach((t, c) => {
|
|
4910
|
+
b[9][c] = { t, c: "r" };
|
|
4911
|
+
});
|
|
4912
|
+
b[7][1] = { t: "c", c: "r" };
|
|
4913
|
+
b[7][7] = { t: "c", c: "r" };
|
|
4914
|
+
[0, 2, 4, 6, 8].forEach((c) => {
|
|
4915
|
+
b[6][c] = { t: "p", c: "r" };
|
|
4916
|
+
});
|
|
4917
|
+
return b;
|
|
4918
|
+
}
|
|
4919
|
+
function cloneBoard(b) {
|
|
4920
|
+
return b.map((row) => row.slice());
|
|
4921
|
+
}
|
|
4922
|
+
function posKey(board) {
|
|
4923
|
+
let s = "";
|
|
4924
|
+
for (const row of board) for (const p of row) s += p ? p.t + p.c : ".";
|
|
4925
|
+
return s;
|
|
4926
|
+
}
|
|
4927
|
+
function inBounds(r, c) {
|
|
4928
|
+
return r >= 0 && r < ROWS2 && c >= 0 && c < COLS2;
|
|
4929
|
+
}
|
|
4930
|
+
function inPalace(r, c, color) {
|
|
4931
|
+
if (c < 3 || c > 5) return false;
|
|
4932
|
+
return color === "r" ? r >= 7 && r <= 9 : r >= 0 && r <= 2;
|
|
4933
|
+
}
|
|
4934
|
+
function ownSide(r, color) {
|
|
4935
|
+
return color === "r" ? r >= 5 : r <= 4;
|
|
4936
|
+
}
|
|
4937
|
+
function crossedRiver(r, color) {
|
|
4938
|
+
return color === "r" ? r <= 4 : r >= 5;
|
|
4939
|
+
}
|
|
4940
|
+
function countBetween(board, r, c, tr, tc, dr, dc) {
|
|
4941
|
+
let n = 0, rr = r + dr, cc = c + dc;
|
|
4942
|
+
while (!(rr === tr && cc === tc)) {
|
|
4943
|
+
if (rr < 0 || rr >= ROWS2 || cc < 0 || cc >= COLS2) return -1;
|
|
4944
|
+
if (board[rr][cc]) n += 1;
|
|
4945
|
+
rr += dr;
|
|
4946
|
+
cc += dc;
|
|
4947
|
+
}
|
|
4948
|
+
return n;
|
|
4949
|
+
}
|
|
4950
|
+
function attacks(board, r, c, tr, tc) {
|
|
4951
|
+
const p = board[r][c];
|
|
4952
|
+
if (!p) return false;
|
|
4953
|
+
const dr = Math.sign(tr - r), dc = Math.sign(tc - c);
|
|
4954
|
+
const t = board[tr][tc];
|
|
4955
|
+
if (p.t === "k") {
|
|
4956
|
+
if (r !== tr && c !== tc) return false;
|
|
4957
|
+
return countBetween(board, r, c, tr, tc, dr, dc) === 0;
|
|
4958
|
+
}
|
|
4959
|
+
if (p.t === "c") {
|
|
4960
|
+
if (r !== tr && c !== tc) return false;
|
|
4961
|
+
if (dr === 0 && dc === 0) return false;
|
|
4962
|
+
return countBetween(board, r, c, tr, tc, dr, dc) === 1 && !!t;
|
|
4963
|
+
}
|
|
4964
|
+
if (p.t === "h") {
|
|
4965
|
+
const adr = Math.abs(tr - r), adc = Math.abs(tc - c);
|
|
4966
|
+
if (!(adr === 2 && adc === 1 || adr === 1 && adc === 2)) return false;
|
|
4967
|
+
const legR = adr === 2 ? r + dr : r;
|
|
4968
|
+
const legC = adc === 2 ? c + dc : c;
|
|
4969
|
+
return !board[legR][legC];
|
|
4970
|
+
}
|
|
4971
|
+
if (p.t === "g") {
|
|
4972
|
+
return Math.abs(tr - r) + Math.abs(tc - c) === 1 && inPalace(tr, tc, p.c);
|
|
4973
|
+
}
|
|
4974
|
+
if (p.t === "a") {
|
|
4975
|
+
return Math.abs(tr - r) === 1 && Math.abs(tc - c) === 1 && inPalace(tr, tc, p.c);
|
|
4976
|
+
}
|
|
4977
|
+
if (p.t === "e") {
|
|
4978
|
+
const adr = Math.abs(tr - r), adc = Math.abs(tc - c);
|
|
4979
|
+
if (!(adr === 2 && adc === 2)) return false;
|
|
4980
|
+
if (!ownSide(tr, p.c)) return false;
|
|
4981
|
+
const eyeR = r + dr, eyeC = c + dc;
|
|
4982
|
+
return !board[eyeR][eyeC];
|
|
4983
|
+
}
|
|
4984
|
+
if (p.t === "p") {
|
|
4985
|
+
const forward = p.c === "r" ? r - 1 : r + 1;
|
|
4986
|
+
if (tr === forward && tc === c) return true;
|
|
4987
|
+
if (crossedRiver(r, p.c) && tr === r && Math.abs(tc - c) === 1) return true;
|
|
4988
|
+
return false;
|
|
4989
|
+
}
|
|
4990
|
+
return false;
|
|
4991
|
+
}
|
|
4992
|
+
function findGeneral(board, color) {
|
|
4993
|
+
for (let r = 0; r < ROWS2; r += 1) {
|
|
4994
|
+
for (let c = 0; c < COLS2; c += 1) {
|
|
4995
|
+
const p = board[r][c];
|
|
4996
|
+
if (p && p.t === "g" && p.c === color) return { r, c };
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
return null;
|
|
5000
|
+
}
|
|
5001
|
+
function pseudoMoves(board, r, c) {
|
|
5002
|
+
const p = board[r][c];
|
|
5003
|
+
if (!p) return [];
|
|
5004
|
+
const out = [];
|
|
5005
|
+
const push = (nr, nc) => {
|
|
5006
|
+
if (inBounds(nr, nc)) out.push([nr, nc]);
|
|
5007
|
+
};
|
|
5008
|
+
const clear = (nr, nc) => !board[nr][nc];
|
|
5009
|
+
const enemy = (nr, nc) => {
|
|
5010
|
+
const q = board[nr][nc];
|
|
5011
|
+
return q && q.c !== p.c;
|
|
5012
|
+
};
|
|
5013
|
+
if (p.t === "g") {
|
|
5014
|
+
[[-1, 0], [1, 0], [0, -1], [0, 1]].forEach(([dr, dc]) => {
|
|
5015
|
+
const nr = r + dr, nc = c + dc;
|
|
5016
|
+
if (inPalace(nr, nc, p.c) && (clear(nr, nc) || enemy(nr, nc))) push(nr, nc);
|
|
5017
|
+
});
|
|
5018
|
+
}
|
|
5019
|
+
if (p.t === "a") {
|
|
5020
|
+
[[-1, -1], [-1, 1], [1, -1], [1, 1]].forEach(([dr, dc]) => {
|
|
5021
|
+
const nr = r + dr, nc = c + dc;
|
|
5022
|
+
if (inPalace(nr, nc, p.c) && (clear(nr, nc) || enemy(nr, nc))) push(nr, nc);
|
|
5023
|
+
});
|
|
5024
|
+
}
|
|
5025
|
+
if (p.t === "e") {
|
|
5026
|
+
[[-2, -2], [-2, 2], [2, -2], [2, 2]].forEach(([dr, dc]) => {
|
|
5027
|
+
const nr = r + dr, nc = c + dc, er = r + dr / 2, ec = c + dc / 2;
|
|
5028
|
+
if (inBounds(nr, nc) && ownSide(nr, p.c) && !board[er][ec] && (clear(nr, nc) || enemy(nr, nc))) push(nr, nc);
|
|
5029
|
+
});
|
|
5030
|
+
}
|
|
5031
|
+
if (p.t === "h") {
|
|
5032
|
+
[[-2, -1], [-2, 1], [2, -1], [2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2]].forEach(([dr, dc]) => {
|
|
5033
|
+
const nr = r + dr, nc = c + dc;
|
|
5034
|
+
const legR = Math.abs(dr) === 2 ? r + dr / 2 : r;
|
|
5035
|
+
const legC = Math.abs(dc) === 2 ? c + dc / 2 : c;
|
|
5036
|
+
if (inBounds(nr, nc) && !board[legR][legC] && (clear(nr, nc) || enemy(nr, nc))) push(nr, nc);
|
|
5037
|
+
});
|
|
5038
|
+
}
|
|
5039
|
+
if (p.t === "k" || p.t === "c") {
|
|
5040
|
+
[[-1, 0], [1, 0], [0, -1], [0, 1]].forEach(([dr, dc]) => {
|
|
5041
|
+
let rr = r + dr, cc = c + dc, jumped = false;
|
|
5042
|
+
while (inBounds(rr, cc)) {
|
|
5043
|
+
const q = board[rr][cc];
|
|
5044
|
+
if (p.t === "k") {
|
|
5045
|
+
if (!q) push(rr, cc);
|
|
5046
|
+
else {
|
|
5047
|
+
if (q.c !== p.c) push(rr, cc);
|
|
5048
|
+
break;
|
|
5049
|
+
}
|
|
5050
|
+
} else {
|
|
5051
|
+
if (!q && !jumped) push(rr, cc);
|
|
5052
|
+
else if (q && !jumped) jumped = true;
|
|
5053
|
+
else if (q && jumped) {
|
|
5054
|
+
if (q.c !== p.c) push(rr, cc);
|
|
5055
|
+
break;
|
|
5056
|
+
}
|
|
5057
|
+
}
|
|
5058
|
+
rr += dr;
|
|
5059
|
+
cc += dc;
|
|
5060
|
+
}
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
5063
|
+
if (p.t === "p") {
|
|
5064
|
+
const forward = p.c === "r" ? -1 : 1;
|
|
5065
|
+
[[forward, 0], [0, -1], [0, 1]].forEach(([dr, dc]) => {
|
|
5066
|
+
const nr = r + dr, nc = c + dc;
|
|
5067
|
+
if (!inBounds(nr, nc) || dc !== 0 && !crossedRiver(r, p.c)) return;
|
|
5068
|
+
if (clear(nr, nc) || enemy(nr, nc)) push(nr, nc);
|
|
5069
|
+
});
|
|
5070
|
+
}
|
|
5071
|
+
return out;
|
|
5072
|
+
}
|
|
5073
|
+
function applyMove(board, fr, fc, tr, tc) {
|
|
5074
|
+
const nb = cloneBoard(board);
|
|
5075
|
+
nb[tr][tc] = nb[fr][fc];
|
|
5076
|
+
nb[fr][fc] = null;
|
|
5077
|
+
return nb;
|
|
5078
|
+
}
|
|
5079
|
+
function inCheck(board, color) {
|
|
5080
|
+
const g = findGeneral(board, color);
|
|
5081
|
+
if (!g) return true;
|
|
5082
|
+
const enemy = color === "r" ? "b" : "r";
|
|
5083
|
+
for (let r = 0; r < ROWS2; r += 1) {
|
|
5084
|
+
const p = board[r][g.c];
|
|
5085
|
+
if (p && p.t === "g" && p.c === enemy) {
|
|
5086
|
+
let clear = true;
|
|
5087
|
+
const lo = Math.min(r, g.r), hi = Math.max(r, g.r);
|
|
5088
|
+
for (let i = lo + 1; i < hi; i += 1) if (board[i][g.c]) {
|
|
5089
|
+
clear = false;
|
|
5090
|
+
break;
|
|
5091
|
+
}
|
|
5092
|
+
if (clear) return true;
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
for (let r = 0; r < ROWS2; r += 1) {
|
|
5096
|
+
for (let c = 0; c < COLS2; c += 1) {
|
|
5097
|
+
const p = board[r][c];
|
|
5098
|
+
if (p && p.c === enemy && attacks(board, r, c, g.r, g.c)) return true;
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
return false;
|
|
5102
|
+
}
|
|
5103
|
+
function legalMoves(board, color) {
|
|
5104
|
+
const out = [];
|
|
5105
|
+
for (let r = 0; r < ROWS2; r += 1) {
|
|
5106
|
+
for (let c = 0; c < COLS2; c += 1) {
|
|
5107
|
+
const p = board[r][c];
|
|
5108
|
+
if (!p || p.c !== color) continue;
|
|
5109
|
+
for (const [nr, nc] of pseudoMoves(board, r, c)) {
|
|
5110
|
+
const nb = applyMove(board, r, c, nr, nc);
|
|
5111
|
+
if (!inCheck(nb, color)) out.push([r, c, nr, nc]);
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
}
|
|
5115
|
+
return out;
|
|
5116
|
+
}
|
|
5117
|
+
function hasLegalMoves(board, color) {
|
|
5118
|
+
return legalMoves(board, color).length > 0;
|
|
5119
|
+
}
|
|
5120
|
+
function evaluatePosition(board) {
|
|
5121
|
+
let score = 0;
|
|
5122
|
+
for (let r = 0; r < ROWS2; r += 1) {
|
|
5123
|
+
for (let c = 0; c < COLS2; c += 1) {
|
|
5124
|
+
const p = board[r][c];
|
|
5125
|
+
if (!p) continue;
|
|
5126
|
+
let v = VALUE[p.t];
|
|
5127
|
+
if (p.t === "p") v += crossedRiver(r, p.c) ? 50 : 0;
|
|
5128
|
+
score += p.c === "b" ? v : -v;
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
return score;
|
|
5132
|
+
}
|
|
5133
|
+
function evalFrom(board, color) {
|
|
5134
|
+
const e = evaluatePosition(board);
|
|
5135
|
+
return color === "b" ? e : -e;
|
|
5136
|
+
}
|
|
5137
|
+
function negamax(board, color, depth, alpha, beta) {
|
|
5138
|
+
const moves = legalMoves(board, color);
|
|
5139
|
+
if (moves.length === 0) {
|
|
5140
|
+
return color === "b" ? -1e6 + (10 - depth) : 1e6 - (10 - depth);
|
|
5141
|
+
}
|
|
5142
|
+
if (depth === 0) return evalFrom(board, color);
|
|
5143
|
+
const enemy = color === "r" ? "b" : "r";
|
|
5144
|
+
const ordered = orderMoves(board, moves);
|
|
5145
|
+
let best = -Infinity;
|
|
5146
|
+
for (const m of ordered) {
|
|
5147
|
+
const nb = applyMove(board, m[0], m[1], m[2], m[3]);
|
|
5148
|
+
const score = -negamax(nb, enemy, depth - 1, -beta, -alpha);
|
|
5149
|
+
if (score > best) best = score;
|
|
5150
|
+
if (best > alpha) alpha = best;
|
|
5151
|
+
if (alpha >= beta) break;
|
|
5152
|
+
}
|
|
5153
|
+
return best;
|
|
5154
|
+
}
|
|
5155
|
+
function orderMoves(board, moves) {
|
|
5156
|
+
const scored = moves.map((m) => {
|
|
5157
|
+
let s = 0;
|
|
5158
|
+
const target = board[m[2]][m[3]];
|
|
5159
|
+
if (target) s += VALUE[target.t] * 10;
|
|
5160
|
+
return { m, s };
|
|
5161
|
+
});
|
|
5162
|
+
scored.sort((a, b) => b.s - a.s);
|
|
5163
|
+
return scored.map((o) => o.m);
|
|
5164
|
+
}
|
|
5165
|
+
function bestMove(board, color, depth, avoid) {
|
|
5166
|
+
const moves = legalMoves(board, color);
|
|
5167
|
+
if (!moves.length) return null;
|
|
5168
|
+
const enemy = color === "r" ? "b" : "r";
|
|
5169
|
+
const ordered = orderMoves(board, moves);
|
|
5170
|
+
let pool;
|
|
5171
|
+
if (avoid && avoid.size) {
|
|
5172
|
+
const seenCount = (m) => avoid.get(posKey(applyMove(board, m[0], m[1], m[2], m[3]))) || 0;
|
|
5173
|
+
const minSeen = Math.min(...ordered.map(seenCount));
|
|
5174
|
+
pool = minSeen === 0 ? ordered.filter((m) => seenCount(m) === 0).map((m) => ({ m, pen: 0 })) : ordered.map((m) => ({ m, pen: seenCount(m) * 60 })).sort((a, b) => a.pen - b.pen);
|
|
5175
|
+
} else {
|
|
5176
|
+
pool = ordered.map((m) => ({ m, pen: 0 }));
|
|
5177
|
+
}
|
|
5178
|
+
if (depth <= 1) {
|
|
5179
|
+
return pickAmongBest(pool.map(({ m, pen }) => ({ m, s: evalFrom(applyMove(board, m[0], m[1], m[2], m[3]), color) - pen })));
|
|
5180
|
+
}
|
|
5181
|
+
let bestScore = -Infinity;
|
|
5182
|
+
let alpha = -Infinity;
|
|
5183
|
+
const scored = [];
|
|
5184
|
+
for (const { m, pen } of pool) {
|
|
5185
|
+
const nb = applyMove(board, m[0], m[1], m[2], m[3]);
|
|
5186
|
+
const s = -negamax(nb, enemy, depth - 1, -Infinity, -alpha) - pen;
|
|
5187
|
+
scored.push({ m, s });
|
|
5188
|
+
if (s > bestScore) {
|
|
5189
|
+
bestScore = s;
|
|
5190
|
+
alpha = Math.max(alpha, bestScore - TIE_EPSILON);
|
|
5191
|
+
}
|
|
5192
|
+
}
|
|
5193
|
+
return pickAmongBest(scored);
|
|
5194
|
+
}
|
|
5195
|
+
var TIE_EPSILON = 12;
|
|
5196
|
+
function pickAmongBest(scored) {
|
|
5197
|
+
let bestScore = -Infinity;
|
|
5198
|
+
for (const o of scored) if (o.s > bestScore) bestScore = o.s;
|
|
5199
|
+
const ties = scored.filter((o) => o.s >= bestScore - TIE_EPSILON);
|
|
5200
|
+
return ties[Math.floor(Math.random() * ties.length)].m;
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5203
|
+
// features/games/games/xiangqi.jsx
|
|
5204
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
5205
|
+
var XIANGQI_LINES = (() => {
|
|
5206
|
+
const P = (n) => n * 100 + 50;
|
|
5207
|
+
const parts = [];
|
|
5208
|
+
let k = 0;
|
|
5209
|
+
parts.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: P(0), y: P(0), width: 800, height: 900, strokeWidth: 7, fill: "none" }, "frame"));
|
|
5210
|
+
for (let r = 1; r <= 8; r += 1) parts.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: P(0), y1: P(r), x2: P(8), y2: P(r) }, k++));
|
|
5211
|
+
for (let c = 1; c <= 7; c += 1) {
|
|
5212
|
+
parts.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: P(c), y1: P(0), x2: P(c), y2: P(4) }, k++));
|
|
5213
|
+
parts.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: P(c), y1: P(5), x2: P(c), y2: P(9) }, k++));
|
|
5214
|
+
}
|
|
5215
|
+
parts.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: `M${P(3)} ${P(0)}L${P(5)} ${P(2)}M${P(5)} ${P(0)}L${P(3)} ${P(2)}M${P(3)} ${P(7)}L${P(5)} ${P(9)}M${P(5)} ${P(7)}L${P(3)} ${P(9)}` }, k++));
|
|
5216
|
+
const t = 14, o = 16;
|
|
5217
|
+
const marks = [[1, 2], [7, 2], [1, 7], [7, 7], [0, 3], [2, 3], [4, 3], [6, 3], [8, 3], [0, 6], [2, 6], [4, 6], [6, 6], [8, 6]];
|
|
5218
|
+
for (const [c, r] of marks) {
|
|
5219
|
+
const seg = [];
|
|
5220
|
+
if (c > 0) seg.push(
|
|
5221
|
+
`M${P(c) - o - t} ${P(r) - o}H${P(c) - o}V${P(r) - o - t}`,
|
|
5222
|
+
`M${P(c) - o - t} ${P(r) + o}H${P(c) - o}V${P(r) + o + t}`
|
|
5223
|
+
);
|
|
5224
|
+
if (c < 8) seg.push(
|
|
5225
|
+
`M${P(c) + o} ${P(r) - o}H${P(c) + o + t}V${P(r) - o - t}`,
|
|
5226
|
+
`M${P(c) + o} ${P(r) + o}H${P(c) + o + t}V${P(r) + o + t}`
|
|
5227
|
+
);
|
|
5228
|
+
parts.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { className: "dgame-xiangqi-mark", strokeWidth: 2.6, d: seg.join("") }, k++));
|
|
5229
|
+
}
|
|
5230
|
+
return parts;
|
|
5231
|
+
})();
|
|
5232
|
+
function XiangqiGame(props) {
|
|
5233
|
+
const [board, setBoard] = (0, import_react13.useState)(initialBoard);
|
|
5234
|
+
const [turn, setTurn] = (0, import_react13.useState)("r");
|
|
5235
|
+
const [sel, setSel] = (0, import_react13.useState)(null);
|
|
5236
|
+
const [targets, setTargets] = (0, import_react13.useState)([]);
|
|
5237
|
+
const [over, setOver] = (0, import_react13.useState)(null);
|
|
5238
|
+
const [check, setCheck] = (0, import_react13.useState)(false);
|
|
5239
|
+
const [last, setLast] = (0, import_react13.useState)(null);
|
|
5240
|
+
const [thinking, setThinking] = (0, import_react13.useState)(false);
|
|
5241
|
+
const [history, setHistory] = (0, import_react13.useState)([]);
|
|
5242
|
+
const [repKeys, setRepKeys] = (0, import_react13.useState)(() => [posKey(initialBoard())]);
|
|
5243
|
+
const pushKey = (0, import_react13.useCallback)((b) => setRepKeys((ks) => ks[ks.length - 1] === posKey(b) ? ks : ks.concat(posKey(b))), []);
|
|
5244
|
+
const stageRef = (0, import_react13.useRef)(null);
|
|
5245
|
+
const boardRef = (0, import_react13.useRef)(board);
|
|
5246
|
+
boardRef.current = board;
|
|
5247
|
+
const turnRef = (0, import_react13.useRef)(turn);
|
|
5248
|
+
turnRef.current = turn;
|
|
5249
|
+
const overRef = (0, import_react13.useRef)(over);
|
|
5250
|
+
overRef.current = over;
|
|
5251
|
+
const thinkingRef = (0, import_react13.useRef)(thinking);
|
|
5252
|
+
thinkingRef.current = thinking;
|
|
5253
|
+
const historyRef = (0, import_react13.useRef)(history);
|
|
5254
|
+
historyRef.current = history;
|
|
5255
|
+
const restart = (0, import_react13.useCallback)(() => {
|
|
5256
|
+
const b = initialBoard();
|
|
5257
|
+
boardRef.current = b;
|
|
5258
|
+
setBoard(b);
|
|
5259
|
+
setTurn("r");
|
|
5260
|
+
setSel(null);
|
|
5261
|
+
setTargets([]);
|
|
5262
|
+
setOver(null);
|
|
5263
|
+
setCheck(false);
|
|
5264
|
+
setLast(null);
|
|
5265
|
+
setThinking(false);
|
|
5266
|
+
setHistory([]);
|
|
5267
|
+
setRepKeys([posKey(b)]);
|
|
5268
|
+
}, []);
|
|
5269
|
+
const undo2 = (0, import_react13.useCallback)(() => {
|
|
5270
|
+
if (thinkingRef.current || overRef.current || turnRef.current !== "r") return;
|
|
5271
|
+
const h = historyRef.current;
|
|
5272
|
+
if (h.length < 2) return;
|
|
5273
|
+
const hist = h.slice(0, -2);
|
|
5274
|
+
const b = hist.length ? cloneBoard(hist[hist.length - 1]) : initialBoard();
|
|
5275
|
+
boardRef.current = b;
|
|
5276
|
+
setBoard(b);
|
|
5277
|
+
setHistory(hist);
|
|
5278
|
+
setSel(null);
|
|
5279
|
+
setTargets([]);
|
|
5280
|
+
setLast(null);
|
|
5281
|
+
setOver(null);
|
|
5282
|
+
setCheck(inCheck(b, "r"));
|
|
5283
|
+
setRepKeys(hist.map(posKey).concat(posKey(b)));
|
|
5284
|
+
}, []);
|
|
5285
|
+
const moveRed = (0, import_react13.useCallback)((tr, tc) => {
|
|
5286
|
+
if (turnRef.current !== "r" || overRef.current || !sel) return;
|
|
5287
|
+
const fr = sel[0], fc = sel[1];
|
|
5288
|
+
if (boardRef.current[tr][tc]) playSfx("capture");
|
|
5289
|
+
else playSfx("step");
|
|
5290
|
+
const nb = applyMove(boardRef.current, fr, fc, tr, tc);
|
|
5291
|
+
setHistory((h) => [...h, boardRef.current]);
|
|
5292
|
+
boardRef.current = nb;
|
|
5293
|
+
pushKey(nb);
|
|
5294
|
+
setBoard(nb);
|
|
5295
|
+
setSel(null);
|
|
5296
|
+
setTargets([]);
|
|
5297
|
+
setLast([tr, tc]);
|
|
5298
|
+
setTurn("b");
|
|
5299
|
+
if (!hasLegalMoves(nb, "b")) {
|
|
5300
|
+
setOver("r");
|
|
5301
|
+
setCheck(false);
|
|
5302
|
+
} else setCheck(inCheck(nb, "b"));
|
|
5303
|
+
}, [sel, pushKey]);
|
|
5304
|
+
const select = (0, import_react13.useCallback)((r, c) => {
|
|
5305
|
+
if (overRef.current || turnRef.current !== "r" || thinkingRef.current) return;
|
|
5306
|
+
const p = boardRef.current[r][c];
|
|
5307
|
+
if (p && p.c === "r") {
|
|
5308
|
+
setSel([r, c]);
|
|
5309
|
+
const list = legalMoves(boardRef.current, "r").filter((m) => m[0] === r && m[1] === c).map((m) => [m[2], m[3]]);
|
|
5310
|
+
setTargets(list);
|
|
5311
|
+
return;
|
|
5312
|
+
}
|
|
5313
|
+
if (sel && targets.some(([tr, tc]) => tr === r && tc === c)) {
|
|
5314
|
+
moveRed(r, c);
|
|
5315
|
+
return;
|
|
5316
|
+
}
|
|
5317
|
+
setSel(null);
|
|
5318
|
+
setTargets([]);
|
|
5319
|
+
}, [sel, targets, moveRed]);
|
|
5320
|
+
(0, import_react13.useEffect)(() => {
|
|
5321
|
+
if (turn !== "b" || over || props.paused) return;
|
|
5322
|
+
const t = setTimeout(() => {
|
|
5323
|
+
const seenCounts = /* @__PURE__ */ new Map();
|
|
5324
|
+
for (const k of repKeys) seenCounts.set(k, (seenCounts.get(k) || 0) + 1);
|
|
5325
|
+
const mv = bestMove(boardRef.current, "b", 3, seenCounts);
|
|
5326
|
+
if (mv) {
|
|
5327
|
+
const [fr, fc, tr, tc] = mv;
|
|
5328
|
+
if (boardRef.current[tr][tc]) playSfx("capture");
|
|
5329
|
+
else playSfx("step");
|
|
5330
|
+
const nb = applyMove(boardRef.current, fr, fc, tr, tc);
|
|
5331
|
+
setHistory((h) => [...h, boardRef.current]);
|
|
5332
|
+
boardRef.current = nb;
|
|
5333
|
+
pushKey(nb);
|
|
5334
|
+
setBoard(nb);
|
|
5335
|
+
setLast([tr, tc]);
|
|
5336
|
+
setTurn("r");
|
|
5337
|
+
if (!hasLegalMoves(nb, "r")) {
|
|
5338
|
+
setOver("b");
|
|
5339
|
+
setCheck(false);
|
|
5340
|
+
} else setCheck(inCheck(nb, "r"));
|
|
5341
|
+
} else setOver("r");
|
|
5342
|
+
setThinking(false);
|
|
5343
|
+
}, 90);
|
|
5344
|
+
setThinking(true);
|
|
5345
|
+
return () => clearTimeout(t);
|
|
5346
|
+
}, [turn, over, props.paused, repKeys, pushKey]);
|
|
5347
|
+
const onKeyDown = (0, import_react13.useCallback)((event) => {
|
|
5348
|
+
if (event.key.toLowerCase() === "r") {
|
|
5349
|
+
event.preventDefault();
|
|
5350
|
+
restart();
|
|
5351
|
+
} else if (event.key.toLowerCase() === "u") {
|
|
5352
|
+
event.preventDefault();
|
|
5353
|
+
undo2();
|
|
5354
|
+
}
|
|
5355
|
+
}, [restart, undo2]);
|
|
5356
|
+
useGameControls(stageRef, props.paused, onKeyDown);
|
|
5357
|
+
const status = over === "r" ? "\u7EA2\u65B9\u80DC \xB7 \u606D\u559C" : over === "b" ? "\u9ED1\u65B9\u80DC \xB7 AI \u8D62" : thinking ? "AI \u601D\u8003\u4E2D\u2026" : check ? "\u5C06\u519B\uFF01" : turn === "r" ? "\u8F6E\u5230\u4F60\uFF08\u7EA2\u65B9\uFF09" : "\u8F6E\u5230 AI\uFF08\u9ED1\u65B9\uFF09";
|
|
5358
|
+
const prevCheck = (0, import_react13.useRef)(false);
|
|
5359
|
+
(0, import_react13.useEffect)(() => {
|
|
5360
|
+
if (over === "r") playSfx("win");
|
|
5361
|
+
else if (over === "b") playSfx("over");
|
|
5362
|
+
else if (!thinking && check && !prevCheck.current) playSfx("check");
|
|
5363
|
+
prevCheck.current = !thinking && check;
|
|
5364
|
+
}, [check, thinking, over]);
|
|
5365
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "dgame-game", "aria-label": "\u8C61\u68CB", children: [
|
|
5366
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dgame-game-head", children: [
|
|
5367
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
|
|
5368
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: "\u4E2D\u56FD\u8C61\u68CB" }),
|
|
5369
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "\u4F60\u6267\u7EA2\u5148\u884C\uFF0CAI \u6267\u9ED1\uFF1B\u5403\u6389\u5BF9\u65B9\u5C06/\u5E05\u5373\u80DC\u3002\u70B9\u51FB\u9009\u4E2D\u7EA2\u5B50\uFF0C\u518D\u70B9\u9AD8\u4EAE\u843D\u70B9\u8D70\u68CB\u3002" })
|
|
5370
|
+
] }),
|
|
5371
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dgame-score", children: [
|
|
5372
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "\u5F53\u524D" }),
|
|
5373
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { className: "dg-gomoku-status", children: status }),
|
|
5374
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: undo2, children: "\u64A4\u9500" }),
|
|
5375
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: restart, children: "\u91CD\u5F00" })
|
|
5376
|
+
] })
|
|
5377
|
+
] }),
|
|
5378
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dgame-xiangqi-board", ref: stageRef, tabIndex: 0, onKeyDown, onClick: () => focusStage(stageRef), role: "grid", "aria-label": "\u4E2D\u56FD\u8C61\u68CB\u68CB\u76D8", children: [
|
|
5379
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { className: "dgame-xiangqi-lines", viewBox: "0 0 900 1000", preserveAspectRatio: "none", "aria-hidden": "true", children: [
|
|
5380
|
+
XIANGQI_LINES,
|
|
5381
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("text", { className: "dgame-xiangqi-rivertext", x: 230, y: 505, children: "\u695A \u6CB3" }),
|
|
5382
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("text", { className: "dgame-xiangqi-rivertext", x: 670, y: 505, children: "\u6F22 \u754C" })
|
|
5383
|
+
] }),
|
|
5384
|
+
board.map((row, r) => row.map((p, c) => {
|
|
5385
|
+
const isSel = sel && sel[0] === r && sel[1] === c;
|
|
5386
|
+
const isTargetMark = targets.some(([tr, tc]) => tr === r && tc === c);
|
|
5387
|
+
const isLast = last && last[0] === r && last[1] === c;
|
|
5388
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
5389
|
+
"button",
|
|
5390
|
+
{
|
|
5391
|
+
type: "button",
|
|
5392
|
+
role: "gridcell",
|
|
5393
|
+
className: "dgame-xiangqi-cell" + (isSel ? " sel" : "") + (isTargetMark ? " target" : "") + (isLast ? " last" : ""),
|
|
5394
|
+
"aria-label": "\u7B2C" + (r + 1) + "\u884C\u7B2C" + (c + 1) + "\u5217" + (p ? pieceChar(p) : " \u7A7A"),
|
|
5395
|
+
onClick: () => {
|
|
5396
|
+
focusStage(stageRef);
|
|
5397
|
+
select(r, c);
|
|
5398
|
+
},
|
|
5399
|
+
children: p ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dgame-xiangqi-pc " + p.c, children: pieceChar(p) }) : isTargetMark ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dgame-xiangqi-dot", "aria-hidden": "true" }) : null
|
|
5400
|
+
},
|
|
5401
|
+
r + "-" + c
|
|
5402
|
+
);
|
|
5403
|
+
}))
|
|
5404
|
+
] }),
|
|
5405
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dgame-controls", children: [
|
|
5406
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: undo2, children: "\u64A4\u9500" }),
|
|
5407
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
5408
|
+
status,
|
|
5409
|
+
" \xB7 R \u91CD\u5F00 \xB7 U \u64A4\u9500"
|
|
5410
|
+
] }),
|
|
5411
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: restart, children: "\u91CD\u5F00" })
|
|
5412
|
+
] })
|
|
5413
|
+
] });
|
|
5414
|
+
}
|
|
5415
|
+
function XiangqiPreview() {
|
|
5416
|
+
const P = (n) => n * 100 + 50;
|
|
5417
|
+
const pc = (c, r, ch, col) => [
|
|
5418
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: P(c), cy: P(r), r: 44, className: "dgp-xq-pc-" + col }, "pc" + c + r),
|
|
5419
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("text", { x: P(c), y: P(r) + 2, className: "dgp-xq-tx-" + col, children: ch }, "tx" + c + r)
|
|
5420
|
+
];
|
|
5421
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dgcov-prev dgcov-prev-xiangqi", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { viewBox: "0 0 900 1000", preserveAspectRatio: "xMidYMid slice", children: [
|
|
5422
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: P(0), y: P(0), width: 800, height: 900, fill: "none", strokeWidth: 11 }),
|
|
5423
|
+
[1, 2, 3, 4, 5, 6, 7, 8].map((r) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: P(0), y1: P(r), x2: P(8), y2: P(r) }, "h" + r)),
|
|
5424
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: P(0), y1: P(0), x2: P(0), y2: P(9) }),
|
|
5425
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: P(8), y1: P(0), x2: P(8), y2: P(9) }),
|
|
5426
|
+
[1, 2, 3, 4, 5, 6, 7].map((c) => [
|
|
5427
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: P(c), y1: P(0), x2: P(c), y2: P(4) }, "vt" + c),
|
|
5428
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: P(c), y1: P(5), x2: P(c), y2: P(9) }, "vb" + c)
|
|
5429
|
+
]),
|
|
5430
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: `M${P(3)} ${P(0)}L${P(5)} ${P(2)}M${P(5)} ${P(0)}L${P(3)} ${P(2)}M${P(3)} ${P(7)}L${P(5)} ${P(9)}M${P(5)} ${P(7)}L${P(3)} ${P(9)}` }),
|
|
5431
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("text", { className: "dgp-xq-river", x: 235, y: 500, children: "\u695A\u6CB3" }),
|
|
5432
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("text", { className: "dgp-xq-river", x: 665, y: 500, children: "\u6F22\u754C" }),
|
|
5433
|
+
pc(4, 0, "\u5C07", "b"),
|
|
5434
|
+
pc(1, 2, "\u70AE", "b"),
|
|
5435
|
+
pc(5, 3, "\u5352", "b"),
|
|
5436
|
+
pc(3, 6, "\u5175", "r"),
|
|
5437
|
+
pc(7, 7, "\u70AE", "r"),
|
|
5438
|
+
pc(4, 9, "\u5E25", "r")
|
|
5439
|
+
] }) });
|
|
5440
|
+
}
|
|
5441
|
+
var xiangqiGame = { Game: XiangqiGame, Preview: XiangqiPreview, css: `
|
|
5442
|
+
.dgame-xiangqi-board{position:relative;display:grid;grid-template-columns:repeat(9,1fr);grid-template-rows:repeat(10,1fr);align-self:center;width:min(100%,min(90vw,340px));aspect-ratio:9/10;padding:13px;border-radius:12px;border:1px solid color-mix(in srgb,#7c4f16 65%,transparent);box-shadow:0 12px 32px rgb(0 0 0/.5),inset 0 1px 0 rgba(255,236,200,.55),inset 0 -3px 9px rgba(90,50,10,.35),inset 3px 0 8px rgba(255,224,170,.28),inset -3px 0 8px rgba(120,70,20,.25);outline:none;background:repeating-linear-gradient(94deg,rgba(124,79,22,.05) 0 2px,transparent 2px 11px),repeating-linear-gradient(87deg,rgba(255,238,196,.05) 0 2px,transparent 2px 14px),radial-gradient(130% 105% at 26% 8%,#f0cd86,#ddb067 48%,#cb9a51 78%,#bb8844)}.dgame-xiangqi-lines{position:absolute;inset:13px;pointer-events:none}.dgame-xiangqi-lines line,.dgame-xiangqi-lines path{stroke:#66431a;stroke-width:3.4;fill:none;stroke-linecap:square}.dgame-xiangqi-lines .dgame-xiangqi-mark{stroke-width:2.6}.dgame-xiangqi-rivertext{fill:rgba(102,67,26,.7);font-family:KaiTi,"Kaiti SC","STKaiti","FangSong",serif;font-size:46px;letter-spacing:7px;text-anchor:middle;dominant-baseline:central}.dgame-xiangqi-board:focus-visible{box-shadow:0 0 0 3px rgba(0,0,0,.25)}.dgame-xiangqi-cell{position:relative;z-index:1;background:transparent;border:none;cursor:pointer;padding:0;margin:0;outline:none;display:grid;place-items:center;min-width:0;min-height:0}.dgame-xiangqi-cell.target .dgame-xiangqi-dot{width:12px;height:12px;border-radius:50%;background:rgba(34,211,238,.6);box-shadow:0 0 10px rgba(34,211,238,.65),0 0 0 2px rgba(255,255,255,.28)}.dgame-xiangqi-cell.target .dgame-xiangqi-pc{box-shadow:0 0 0 2px rgba(248,113,113,.85),0 0 10px rgba(248,113,113,.5),0 2px 4px rgb(0 0 0/.4),inset 0 0 0 1.5px rgba(96,62,18,.5),inset 0 1px 2px rgba(255,255,255,.95),inset 0 -2px 3px rgba(120,80,20,.4)}.dgame-xiangqi-cell.sel .dgame-xiangqi-pc::before{content:'';position:absolute;inset:-3px;border-radius:50%;border:2.5px solid rgba(251,191,36,.95);box-shadow:0 0 12px rgba(251,191,36,.6)}.dgame-xiangqi-cell.last .dgame-xiangqi-pc::after{content:'';position:absolute;right:5%;bottom:5%;width:5px;height:5px;border-radius:50%;background:#ef4444;box-shadow:0 0 4px rgba(239,68,68,.8)}.dgame-xiangqi-pc{position:relative;display:grid;place-items:center;z-index:1;width:min(80%,30px);aspect-ratio:1/1;border-radius:50%;box-sizing:border-box;font-family:KaiTi,"Kaiti SC","STKaiti","FangSong","SimSun",serif;font-size:clamp(12px,1.4vw,15px);font-weight:700;line-height:1;background:radial-gradient(circle at 34% 28%,#fff3d4,#eedaa6 46%,#d8b675 72%,#bd9650);box-shadow:0 2px 4px rgb(0 0 0/.45),inset 0 0 0 1.5px rgba(96,62,18,.5),inset 0 1px 2px rgba(255,255,255,.95),inset 0 -2px 3px rgba(120,80,20,.4)}.dgame-xiangqi-pc.r{color:#b91c1c;text-shadow:0 1px 0 rgba(255,255,255,.5)}.dgame-xiangqi-pc.b{color:#1f2937;text-shadow:0 1px 0 rgba(255,255,255,.35)}
|
|
5443
|
+
` };
|
|
5444
|
+
|
|
5445
|
+
// features/games/games/snake.jsx
|
|
5446
|
+
var import_react14 = require("react");
|
|
5447
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
5448
|
+
var SIZE2 = 16;
|
|
5449
|
+
var DIRS3 = { ArrowUp: [-1, 0], ArrowDown: [1, 0], ArrowLeft: [0, -1], ArrowRight: [0, 1], w: [-1, 0], s: [1, 0], a: [0, -1], d: [0, 1], W: [-1, 0], S: [1, 0], A: [0, -1], D: [0, 1] };
|
|
5450
|
+
var OPPOSITE = { "-1,0": [1, 0], "1,0": [-1, 0], "0,1": [0, -1], "0,-1": [0, 1] };
|
|
5451
|
+
var SPEED_MODES = {
|
|
5452
|
+
slow: { label: "\u6162\u901F", stepMs: 4, base: 320, min: 180 },
|
|
5453
|
+
normal: { label: "\u4E2D\u901F", stepMs: 3, base: 220, min: 120 },
|
|
5454
|
+
fast: { label: "\u5FEB\u901F", stepMs: 2, base: 140, min: 80 }
|
|
5455
|
+
};
|
|
5456
|
+
function loadSpeedMode() {
|
|
5457
|
+
try {
|
|
5458
|
+
return SPEED_MODES[localStorage.getItem("dsh-dock.snake.speed")] ? localStorage.getItem("dsh-dock.snake.speed") : "slow";
|
|
5459
|
+
} catch {
|
|
5460
|
+
return "slow";
|
|
5461
|
+
}
|
|
5462
|
+
}
|
|
5463
|
+
function randFood(snake) {
|
|
5464
|
+
while (true) {
|
|
5465
|
+
const r = Math.floor(Math.random() * SIZE2), c = Math.floor(Math.random() * SIZE2);
|
|
5466
|
+
if (!snake.some((s) => s.r === r && s.c === c)) return { r, c };
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
function initial2() {
|
|
5470
|
+
const snake = [{ r: 7, c: 5 }, { r: 7, c: 4 }, { r: 7, c: 3 }];
|
|
5471
|
+
return { snake, dir: [0, 1], food: randFood(snake), score: 0, over: false, started: false };
|
|
5472
|
+
}
|
|
5473
|
+
function step2(state, dir) {
|
|
5474
|
+
const head = state.snake[0];
|
|
5475
|
+
const nr = head.r + dir[0], nc = head.c + dir[1];
|
|
5476
|
+
const body = state.snake.slice(0, -1);
|
|
5477
|
+
if (nr < 0 || nr >= SIZE2 || nc < 0 || nc >= SIZE2 || body.some((s) => s.r === nr && s.c === nc)) {
|
|
5478
|
+
return Object.assign({}, state, { over: true });
|
|
5479
|
+
}
|
|
5480
|
+
const ate = state.food.r === nr && state.food.c === nc;
|
|
5481
|
+
let snake = [{ r: nr, c: nc }, ...state.snake];
|
|
5482
|
+
if (ate) {
|
|
5483
|
+
playSfx("eat");
|
|
5484
|
+
const food = randFood(snake);
|
|
5485
|
+
return Object.assign({}, state, { snake, food, score: state.score + 1 });
|
|
5486
|
+
}
|
|
5487
|
+
snake.pop();
|
|
5488
|
+
return Object.assign({}, state, { snake });
|
|
5489
|
+
}
|
|
5490
|
+
function SnakeGame(props) {
|
|
5491
|
+
const [state, setState] = (0, import_react14.useState)(initial2);
|
|
5492
|
+
const [paused, setPaused] = (0, import_react14.useState)(false);
|
|
5493
|
+
const [speedMode, setSpeedMode] = (0, import_react14.useState)(loadSpeedMode);
|
|
5494
|
+
const stageRef = (0, import_react14.useRef)(null);
|
|
5495
|
+
const dirRef = (0, import_react14.useRef)(state.dir);
|
|
5496
|
+
const overRef = (0, import_react14.useRef)(state.over);
|
|
5497
|
+
overRef.current = state.over;
|
|
5498
|
+
const pausedRef = (0, import_react14.useRef)(!!(props && props.paused) || paused);
|
|
5499
|
+
pausedRef.current = !!(props && props.paused) || paused;
|
|
5500
|
+
const start = (0, import_react14.useCallback)(() => {
|
|
5501
|
+
const init = initial2();
|
|
5502
|
+
init.started = true;
|
|
5503
|
+
dirRef.current = init.dir;
|
|
5504
|
+
setState(init);
|
|
5505
|
+
setPaused(false);
|
|
5506
|
+
}, []);
|
|
5507
|
+
const begin = (0, import_react14.useCallback)(() => {
|
|
5508
|
+
setState((s) => s.started || s.over ? s : Object.assign({}, s, { started: true }));
|
|
5509
|
+
}, []);
|
|
5510
|
+
const changeDir = (0, import_react14.useCallback)((nv) => {
|
|
5511
|
+
if (overRef.current || pausedRef.current) return;
|
|
5512
|
+
const cur = dirRef.current;
|
|
5513
|
+
const opp = OPPOSITE[cur[0] + "," + cur[1]];
|
|
5514
|
+
if (opp && opp[0] === nv[0] && opp[1] === nv[1]) return;
|
|
5515
|
+
dirRef.current = nv;
|
|
5516
|
+
begin();
|
|
5517
|
+
}, [begin]);
|
|
5518
|
+
(0, import_react14.useEffect)(() => {
|
|
5519
|
+
if (state.over) playSfx("over");
|
|
5520
|
+
}, [state.over]);
|
|
5521
|
+
(0, import_react14.useEffect)(() => {
|
|
5522
|
+
if (!state.started || state.over || pausedRef.current) return;
|
|
5523
|
+
const mode = SPEED_MODES[speedMode] || SPEED_MODES.slow;
|
|
5524
|
+
const speed = Math.max(mode.min, mode.base - state.score * mode.stepMs);
|
|
5525
|
+
const timer = setInterval(() => setState((s) => s.over || !s.started || pausedRef.current ? s : step2(s, dirRef.current)), speed);
|
|
5526
|
+
return () => clearInterval(timer);
|
|
5527
|
+
}, [state.started, state.over, state.score, props.paused, paused, speedMode]);
|
|
5528
|
+
const pickSpeed = (0, import_react14.useCallback)((m) => {
|
|
5529
|
+
setSpeedMode(m);
|
|
5530
|
+
try {
|
|
5531
|
+
localStorage.setItem("dsh-dock.snake.speed", m);
|
|
5532
|
+
} catch {
|
|
5533
|
+
}
|
|
5534
|
+
}, []);
|
|
5535
|
+
const onKeyDown = (0, import_react14.useCallback)((event) => {
|
|
5536
|
+
const d = DIRS3[event.key];
|
|
5537
|
+
if (d) {
|
|
5538
|
+
event.preventDefault();
|
|
5539
|
+
changeDir(d);
|
|
5540
|
+
return;
|
|
5541
|
+
}
|
|
5542
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
5543
|
+
event.preventDefault();
|
|
5544
|
+
if (overRef.current || !state.started || pausedRef.current) start();
|
|
5545
|
+
else setPaused((p) => !p);
|
|
5546
|
+
return;
|
|
5547
|
+
}
|
|
5548
|
+
if (event.key.toLowerCase() === "p") {
|
|
5549
|
+
event.preventDefault();
|
|
5550
|
+
setPaused((p) => !p);
|
|
5551
|
+
} else if (event.key.toLowerCase() === "r") {
|
|
5552
|
+
event.preventDefault();
|
|
5553
|
+
start();
|
|
5554
|
+
}
|
|
5555
|
+
}, [changeDir, start, state.started]);
|
|
5556
|
+
useGameControls(stageRef, pausedRef.current, onKeyDown);
|
|
5557
|
+
const cells = [];
|
|
5558
|
+
for (let r = 0; r < SIZE2; r += 1) {
|
|
5559
|
+
for (let c = 0; c < SIZE2; c += 1) {
|
|
5560
|
+
const idx = state.snake.findIndex((s) => s.r === r && s.c === c);
|
|
5561
|
+
const isFood = state.food.r === r && state.food.c === c;
|
|
5562
|
+
let cls = "dgame-snake-cell";
|
|
5563
|
+
if (idx >= 0) cls += " bob" + (idx === 0 ? " head" : idx < 4 ? " body2" : "");
|
|
5564
|
+
if (isFood) cls += " food";
|
|
5565
|
+
cells.push(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", { className: cls, "aria-hidden": "true" }, r + "-" + c));
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { className: "dgame-game", "aria-label": "\u8D2A\u5403\u86C7", children: [
|
|
5569
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dgame-game-head", children: [
|
|
5570
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
5571
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { children: "\u8D2A\u5403\u86C7" }),
|
|
5572
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { children: "\u56DE\u8F66\u5F00\u59CB/\u6682\u505C\uFF0C\u65B9\u5411\u952E\u63A7\u5236\u79FB\u52A8\uFF1B\u5403\u5230\u98DF\u7269\u53D8\u957F\u5E76\u7F13\u6162\u63D0\u901F\uFF0C\u649E\u5899\u6216\u54AC\u5230\u81EA\u5DF1\u5C31\u7ED3\u675F\u3002" })
|
|
5573
|
+
] }),
|
|
5574
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dgame-score", children: [
|
|
5575
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
|
|
5576
|
+
"\u957F\u5EA6 ",
|
|
5577
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: state.score + 3 })
|
|
5578
|
+
] }),
|
|
5579
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
|
|
5580
|
+
"\u5F97\u5206 ",
|
|
5581
|
+
state.score
|
|
5582
|
+
] })
|
|
5583
|
+
] })
|
|
5584
|
+
] }),
|
|
5585
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dgame-snake-speeds", role: "radiogroup", "aria-label": "\u6E38\u620F\u901F\u5EA6", children: [
|
|
5586
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "\u901F\u5EA6" }),
|
|
5587
|
+
Object.entries(SPEED_MODES).map(([m, cfg]) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", role: "radio", "aria-checked": speedMode === m, className: speedMode === m ? "on" : "", onClick: () => pickSpeed(m), children: cfg.label }, m))
|
|
5588
|
+
] }),
|
|
5589
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dgame-snake-stage", ref: stageRef, tabIndex: 0, onKeyDown, onClick: () => focusStage(stageRef), "aria-label": "\u8D2A\u5403\u86C7\u6E38\u620F\u533A\u57DF\uFF0C\u4F7F\u7528\u65B9\u5411\u952E\u63A7\u5236\u79FB\u52A8\uFF0C\u56DE\u8F66\u5F00\u59CB", children: [
|
|
5590
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dgame-snake-grid", children: cells }),
|
|
5591
|
+
state.over ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dgame-over", children: [
|
|
5592
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("strong", { children: [
|
|
5593
|
+
"\u649E\u4E0A\u4E86 \xB7 \u5F97 ",
|
|
5594
|
+
state.score,
|
|
5595
|
+
" \u5206"
|
|
5596
|
+
] }),
|
|
5597
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: start, children: "\u91CD\u65B0\u5F00\u59CB" })
|
|
5598
|
+
] }) : null,
|
|
5599
|
+
!state.started && !state.over ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dgame-over", children: [
|
|
5600
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: "\u6309 Enter \u6216\u65B9\u5411\u952E\u5F00\u59CB" }),
|
|
5601
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: start, children: "\u5F00\u59CB\u6E38\u620F" })
|
|
5602
|
+
] }) : null,
|
|
5603
|
+
state.started && !state.over && pausedRef.current ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dgame-over", children: [
|
|
5604
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: "\u5DF2\u6682\u505C" }),
|
|
5605
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: () => setPaused(false), children: "\u7EE7\u7EED\uFF08P\uFF09" })
|
|
5606
|
+
] }) : null
|
|
5607
|
+
] }),
|
|
5608
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dgame-controls", children: [
|
|
5609
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "aria-label": "\u5411\u4E0A", onClick: () => changeDir([-1, 0]), children: "\u2191" }),
|
|
5610
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "\u65B9\u5411\u952E / WASD \xB7 \u56DE\u8F66 \u5F00\u59CB/\u6682\u505C \xB7 R \u91CD\u5F00" }),
|
|
5611
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "aria-label": "\u5411\u4E0B", onClick: () => changeDir([1, 0]), children: "\u2193" })
|
|
5612
|
+
] })
|
|
5613
|
+
] });
|
|
5614
|
+
}
|
|
5615
|
+
function SnakePreview() {
|
|
5616
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dgcov-prev dgcov-prev-snake", "aria-hidden": "true", children: [
|
|
5617
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", { style: { left: "12%", top: "62%" } }),
|
|
5618
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", { style: { left: "24%", top: "62%" } }),
|
|
5619
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", { style: { left: "36%", top: "62%" } }),
|
|
5620
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", { style: { left: "48%", top: "62%" } }),
|
|
5621
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", { style: { left: "60%", top: "62%" } }),
|
|
5622
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("b", {})
|
|
5623
|
+
] });
|
|
5624
|
+
}
|
|
5625
|
+
var snakeGame = { Game: SnakeGame, Preview: SnakePreview, css: `
|
|
5626
|
+
.dgame-snake-speeds{display:flex;gap:6px;align-items:center;align-self:center;justify-content:center}
|
|
5627
|
+
.dgame-snake-speeds span{font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
5628
|
+
.dgame-snake-speeds button{min-width:44px;height:26px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;cursor:pointer;transition:all .15s ease}
|
|
5629
|
+
.dgame-snake-speeds button.on{color:#bbf7d0;border-color:color-mix(in srgb,#4ade80 55%,transparent);background:color-mix(in srgb,#4ade80 16%,transparent)}
|
|
5630
|
+
.dgame-snake-speeds button:hover{border-color:#4ade80;color:var(--dsw-alias-label-primary)}
|
|
5631
|
+
.dgame-snake-stage{position:relative;align-self:center;width:min(100%,300px);aspect-ratio:1/1;border:1px solid color-mix(in srgb,rgb(74 222 128) 32%,var(--dsw-alias-border-l1));border-radius:10px;overflow:hidden;outline:none;background:radial-gradient(circle at 50% -10%,color-mix(in srgb,#166534 30%,transparent),transparent 55%),#0a1420}.dgame-snake-stage:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,rgb(74 222 128) 35%,transparent)}.dgame-snake-grid{display:grid;grid-template-columns:repeat(16,1fr);grid-template-rows:repeat(16,1fr);width:100%;height:100%}.dgame-snake-cell{position:relative}.dgame-snake-cell.food::after{content:'';position:absolute;inset:22%;border-radius:50%;background:radial-gradient(circle at 35% 30%,#fecaca,#ef4444 70%);box-shadow:0 0 9px color-mix(in srgb,#ef4444 70%,transparent)}.dgame-snake-cell.bob::after{content:'';position:absolute;inset:20%;border-radius:26%;background:linear-gradient(135deg,#4ade80,#16a34a)}.dgame-snake-cell.head::after{background:linear-gradient(135deg,#bbf7d0,#22c55e);border-radius:30% 30% 28% 28%;box-shadow:0 1px 6px color-mix(in srgb,#22c55e 60%,transparent)}.dgame-snake-cell.head::before{content:'';position:absolute;left:30%;top:30%;width:9%;height:9%;border-radius:50%;background:#0b1b12}.dgame-snake-cell.body2::after{background:linear-gradient(135deg,#34d399,#0d9f6e)}
|
|
5632
|
+
` };
|
|
5633
|
+
|
|
5634
|
+
// features/games/games/breakout.jsx
|
|
5635
|
+
var import_react15 = require("react");
|
|
5636
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
5637
|
+
var W = 320;
|
|
5638
|
+
var H = 360;
|
|
5639
|
+
var BRICK_COLS = 8;
|
|
5640
|
+
var BRICK_ROWS = 5;
|
|
5641
|
+
var BRICK_GAP = 4;
|
|
5642
|
+
var BRICK_TOP = 22;
|
|
5643
|
+
var BRICK_H = 18;
|
|
5644
|
+
var PADDLE_W = 60;
|
|
5645
|
+
var PADDLE_H = 11;
|
|
5646
|
+
var PADDLE_BOTTOM = 20;
|
|
5647
|
+
var BALL_R = 5.5;
|
|
5648
|
+
var SPEED = 3.4;
|
|
5649
|
+
var COLORS = ["#f87171", "#fb923c", "#facc15", "#4ade80", "#38bdf8"];
|
|
5650
|
+
function makeBricks(level) {
|
|
5651
|
+
const bricks = [];
|
|
5652
|
+
const bw = (W - BRICK_GAP * (BRICK_COLS + 1)) / BRICK_COLS;
|
|
5653
|
+
for (let r = 0; r < BRICK_ROWS; r += 1) {
|
|
5654
|
+
for (let c = 0; c < BRICK_COLS; c += 1) {
|
|
5655
|
+
bricks.push({
|
|
5656
|
+
x: BRICK_GAP + c * (bw + BRICK_GAP),
|
|
5657
|
+
y: BRICK_TOP + r * (BRICK_H + BRICK_GAP),
|
|
5658
|
+
w: bw,
|
|
5659
|
+
h: BRICK_H,
|
|
5660
|
+
color: COLORS[(r + level) % COLORS.length],
|
|
5661
|
+
alive: true
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
return bricks;
|
|
5666
|
+
}
|
|
5667
|
+
function draw(ctx, g) {
|
|
5668
|
+
ctx.clearRect(0, 0, W, H);
|
|
5669
|
+
ctx.fillStyle = "#0b1024";
|
|
5670
|
+
ctx.fillRect(0, 0, W, H);
|
|
5671
|
+
ctx.fillStyle = "#1b2bb0";
|
|
5672
|
+
for (const s of g.stars) {
|
|
5673
|
+
ctx.globalAlpha = s.a;
|
|
5674
|
+
ctx.fillRect(s.x, s.y, 1.5, 1.5);
|
|
5675
|
+
}
|
|
5676
|
+
ctx.globalAlpha = 1;
|
|
5677
|
+
for (const b of g.bricks) {
|
|
5678
|
+
if (!b.alive) continue;
|
|
5679
|
+
ctx.fillStyle = b.color;
|
|
5680
|
+
ctx.beginPath();
|
|
5681
|
+
roundRect(ctx, b.x, b.y, b.w, b.h, 4);
|
|
5682
|
+
ctx.fill();
|
|
5683
|
+
ctx.fillStyle = "rgba(255,255,255,.25)";
|
|
5684
|
+
roundRect(ctx, b.x, b.y, b.w, b.h / 2.4, 4);
|
|
5685
|
+
ctx.fill();
|
|
5686
|
+
}
|
|
5687
|
+
ctx.fillStyle = "#a5f3fc";
|
|
5688
|
+
roundRect(ctx, g.paddle.x, g.paddle.y, PADDLE_W, PADDLE_H, 5);
|
|
5689
|
+
ctx.fill();
|
|
5690
|
+
ctx.fillStyle = "#fef3c7";
|
|
5691
|
+
ctx.beginPath();
|
|
5692
|
+
ctx.arc(g.ball.x, g.ball.y, BALL_R, 0, Math.PI * 2);
|
|
5693
|
+
ctx.fill();
|
|
5694
|
+
ctx.fillStyle = "rgba(254,243,199,.5)";
|
|
5695
|
+
ctx.beginPath();
|
|
5696
|
+
ctx.arc(g.ball.x, g.ball.y, BALL_R + 2.5, 0, Math.PI * 2);
|
|
5697
|
+
ctx.fill();
|
|
5698
|
+
}
|
|
5699
|
+
function roundRect(ctx, x, y, w, h, r) {
|
|
5700
|
+
ctx.beginPath();
|
|
5701
|
+
ctx.moveTo(x + r, y);
|
|
5702
|
+
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
5703
|
+
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
5704
|
+
ctx.arcTo(x, y + h, x, y, r);
|
|
5705
|
+
ctx.arcTo(x, y, x + w, y, r);
|
|
5706
|
+
ctx.closePath();
|
|
5707
|
+
}
|
|
5708
|
+
function collideRect(x, y, r, bx, by, bw, bh) {
|
|
5709
|
+
const cx = Math.max(bx, Math.min(x, bx + bw));
|
|
5710
|
+
const cy = Math.max(by, Math.min(y, by + bh));
|
|
5711
|
+
const dx = x - cx, dy = y - cy;
|
|
5712
|
+
return dx * dx + dy * dy < r * r;
|
|
5713
|
+
}
|
|
5714
|
+
function BreakoutGame(props) {
|
|
5715
|
+
const canvasRef = (0, import_react15.useRef)(null);
|
|
5716
|
+
const [score, setScore] = (0, import_react15.useState)(0);
|
|
5717
|
+
const [lives, setLives] = (0, import_react15.useState)(3);
|
|
5718
|
+
const [level, setLevel] = (0, import_react15.useState)(1);
|
|
5719
|
+
const [status, setStatus] = (0, import_react15.useState)("play");
|
|
5720
|
+
const keys = (0, import_react15.useRef)({});
|
|
5721
|
+
const pausedRef = (0, import_react15.useRef)(!!(props && props.paused));
|
|
5722
|
+
pausedRef.current = !!(props && props.paused);
|
|
5723
|
+
const game = (0, import_react15.useRef)(null);
|
|
5724
|
+
const scoreRef = (0, import_react15.useRef)(0);
|
|
5725
|
+
scoreRef.current = score;
|
|
5726
|
+
const statusRef = (0, import_react15.useRef)("play");
|
|
5727
|
+
statusRef.current = status;
|
|
5728
|
+
const livesRef = (0, import_react15.useRef)(3);
|
|
5729
|
+
livesRef.current = lives;
|
|
5730
|
+
const start = (0, import_react15.useCallback)((lvl) => {
|
|
5731
|
+
const bricks = makeBricks(lvl);
|
|
5732
|
+
game.current = {
|
|
5733
|
+
stars: Array.from({ length: 46 }, () => ({ x: Math.random() * W, y: Math.random() * H, a: 0.25 + Math.random() * 0.5 })),
|
|
5734
|
+
bricks,
|
|
5735
|
+
paddle: { x: (W - PADDLE_W) / 2, y: H - PADDLE_BOTTOM - PADDLE_H },
|
|
5736
|
+
ball: { x: W / 2, y: H - PADDLE_BOTTOM - PADDLE_H - BALL_R - 1, vx: (Math.random() > 0.5 ? 1 : -1) * SPEED, vy: -SPEED },
|
|
5737
|
+
speed: SPEED * (1 + (lvl - 1) * 0.09)
|
|
5738
|
+
};
|
|
5739
|
+
}, []);
|
|
5740
|
+
(0, import_react15.useEffect)(() => {
|
|
5741
|
+
start(1);
|
|
5742
|
+
setScore(0);
|
|
5743
|
+
setLives(3);
|
|
5744
|
+
setStatus("play");
|
|
5745
|
+
}, [start]);
|
|
5746
|
+
const launchBall = (0, import_react15.useCallback)(() => {
|
|
5747
|
+
const g = game.current;
|
|
5748
|
+
if (!g) return;
|
|
5749
|
+
g.ball.vx = (Math.random() > 0.5 ? 1 : -1) * SPEED;
|
|
5750
|
+
g.ball.vy = -SPEED;
|
|
5751
|
+
g.launched = true;
|
|
5752
|
+
}, []);
|
|
5753
|
+
const resetBall = (0, import_react15.useCallback)(() => {
|
|
5754
|
+
const g = game.current;
|
|
5755
|
+
if (g) {
|
|
5756
|
+
g.ball.x = W / 2;
|
|
5757
|
+
g.ball.y = H - PADDLE_BOTTOM - PADDLE_H - BALL_R - 1;
|
|
5758
|
+
g.ball.vx = 0;
|
|
5759
|
+
g.ball.vy = 0;
|
|
5760
|
+
g.launched = false;
|
|
5761
|
+
}
|
|
5762
|
+
}, []);
|
|
5763
|
+
(0, import_react15.useEffect)(() => {
|
|
5764
|
+
const frame = () => {
|
|
5765
|
+
const g = game.current;
|
|
5766
|
+
const ctx = canvasRef.current && canvasRef.current.getContext("2d");
|
|
5767
|
+
if (!g || !ctx) return;
|
|
5768
|
+
if (pausedRef.current || statusRef.current !== "play") {
|
|
5769
|
+
draw(ctx, g);
|
|
5770
|
+
return;
|
|
5771
|
+
}
|
|
5772
|
+
if (keys.current.left) g.paddle.x -= 5.5;
|
|
5773
|
+
if (keys.current.right) g.paddle.x += 5.5;
|
|
5774
|
+
g.paddle.x = Math.max(0, Math.min(W - PADDLE_W, g.paddle.x));
|
|
5775
|
+
const b = g.ball;
|
|
5776
|
+
if (!b.launched) {
|
|
5777
|
+
b.x = g.paddle.x + PADDLE_W / 2;
|
|
5778
|
+
b.y = g.paddle.y - BALL_R - 1;
|
|
5779
|
+
} else {
|
|
5780
|
+
b.x += b.vx;
|
|
5781
|
+
b.y += b.vy;
|
|
5782
|
+
if (b.x - BALL_R < 0) {
|
|
5783
|
+
b.x = BALL_R;
|
|
5784
|
+
b.vx = Math.abs(b.vx);
|
|
5785
|
+
}
|
|
5786
|
+
if (b.x + BALL_R > W) {
|
|
5787
|
+
b.x = W - BALL_R;
|
|
5788
|
+
b.vx = -Math.abs(b.vx);
|
|
5789
|
+
}
|
|
5790
|
+
if (b.y - BALL_R < 0) {
|
|
5791
|
+
b.y = BALL_R;
|
|
5792
|
+
b.vy = Math.abs(b.vy);
|
|
5793
|
+
}
|
|
5794
|
+
if (b.vy > 0 && b.y + BALL_R >= g.paddle.y && b.y - BALL_R <= g.paddle.y + PADDLE_H && b.x >= g.paddle.x - BALL_R && b.x <= g.paddle.x + PADDLE_W + BALL_R) {
|
|
5795
|
+
playSfx("bounce");
|
|
5796
|
+
const rel = (b.x - (g.paddle.x + PADDLE_W / 2)) / (PADDLE_W / 2);
|
|
5797
|
+
const ang = rel * 0.6;
|
|
5798
|
+
const sp = Math.hypot(b.vx, b.vy);
|
|
5799
|
+
b.vx = sp * Math.sin(ang);
|
|
5800
|
+
b.vy = -Math.abs(sp * Math.cos(ang));
|
|
5801
|
+
b.y = g.paddle.y - BALL_R - 1;
|
|
5802
|
+
}
|
|
5803
|
+
const maxSpeed = g.speed * 1.6;
|
|
5804
|
+
for (const br of g.bricks) {
|
|
5805
|
+
if (!br.alive) continue;
|
|
5806
|
+
if (collideRect(b.x, b.y, BALL_R, br.x, br.y, br.w, br.h)) {
|
|
5807
|
+
br.alive = false;
|
|
5808
|
+
playSfx("brick");
|
|
5809
|
+
const cx = br.x + br.w / 2, cy = br.y + br.h / 2;
|
|
5810
|
+
const dx = b.x - cx, dy = b.y - cy;
|
|
5811
|
+
if (Math.abs(dx / br.w) > Math.abs(dy / br.h)) {
|
|
5812
|
+
b.vx = -b.vx;
|
|
5813
|
+
b.x += b.vx * 2;
|
|
5814
|
+
} else {
|
|
5815
|
+
b.vy = -b.vy;
|
|
5816
|
+
b.y += b.vy * 2;
|
|
5817
|
+
}
|
|
5818
|
+
setScore((s) => s + 10);
|
|
5819
|
+
scoreRef.current += 10;
|
|
5820
|
+
const remain = g.bricks.some((x) => x.alive);
|
|
5821
|
+
if (!remain) {
|
|
5822
|
+
playSfx("win");
|
|
5823
|
+
setStatus("win");
|
|
5824
|
+
setLevel((l) => l + 1);
|
|
5825
|
+
if (statusRef.current !== "over") statusRef.current = "win";
|
|
5826
|
+
}
|
|
5827
|
+
break;
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
if (b.y - BALL_R > H) {
|
|
5831
|
+
const nl = livesRef.current - 1;
|
|
5832
|
+
livesRef.current = nl;
|
|
5833
|
+
setLives(nl);
|
|
5834
|
+
playSfx(nl <= 0 ? "over" : "step");
|
|
5835
|
+
if (nl <= 0) {
|
|
5836
|
+
setStatus("over");
|
|
5837
|
+
statusRef.current = "over";
|
|
5838
|
+
} else resetBall();
|
|
5839
|
+
}
|
|
5840
|
+
}
|
|
5841
|
+
draw(ctx, g);
|
|
5842
|
+
};
|
|
5843
|
+
let raf = requestAnimationFrame(function loop() {
|
|
5844
|
+
frame();
|
|
5845
|
+
raf = requestAnimationFrame(loop);
|
|
5846
|
+
});
|
|
5847
|
+
return () => cancelAnimationFrame(raf);
|
|
5848
|
+
}, [resetBall]);
|
|
5849
|
+
const onKeyDown = (0, import_react15.useCallback)((event) => {
|
|
5850
|
+
if (pausedRef.current) return;
|
|
5851
|
+
if (event.key === "ArrowLeft") {
|
|
5852
|
+
event.preventDefault();
|
|
5853
|
+
keys.current.left = true;
|
|
5854
|
+
} else if (event.key === "ArrowRight") {
|
|
5855
|
+
event.preventDefault();
|
|
5856
|
+
keys.current.right = true;
|
|
5857
|
+
} else if (event.key === " ") {
|
|
5858
|
+
event.preventDefault();
|
|
5859
|
+
launchBall();
|
|
5860
|
+
} else if (event.key.toLowerCase() === "r") {
|
|
5861
|
+
event.preventDefault();
|
|
5862
|
+
start(1);
|
|
5863
|
+
setScore(0);
|
|
5864
|
+
setLives(3);
|
|
5865
|
+
setStatus("play");
|
|
5866
|
+
statusRef.current = "play";
|
|
5867
|
+
livesRef.current = 3;
|
|
5868
|
+
}
|
|
5869
|
+
}, [launchBall, start]);
|
|
5870
|
+
const onKeyUp = (0, import_react15.useCallback)((event) => {
|
|
5871
|
+
if (event.key === "ArrowLeft") keys.current.left = false;
|
|
5872
|
+
else if (event.key === "ArrowRight") keys.current.right = false;
|
|
5873
|
+
}, []);
|
|
5874
|
+
const onPointerMove = (0, import_react15.useCallback)((e) => {
|
|
5875
|
+
const g = game.current, cv = canvasRef.current;
|
|
5876
|
+
if (!g || !cv) return;
|
|
5877
|
+
const rect2 = cv.getBoundingClientRect();
|
|
5878
|
+
const scale = rect2.width / W;
|
|
5879
|
+
g.paddle.x = Math.max(0, Math.min(W - PADDLE_W, (e.clientX - rect2.left) / scale - PADDLE_W / 2));
|
|
5880
|
+
}, []);
|
|
5881
|
+
useGameControls(canvasRef, pausedRef.current, onKeyDown, onKeyUp);
|
|
5882
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "dgame-game", "aria-label": "\u6253\u7816\u5757", children: [
|
|
5883
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dgame-game-head", children: [
|
|
5884
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
|
|
5885
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { children: "\u6253\u7816\u5757" }),
|
|
5886
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: "\u79FB\u52A8\u6321\u677F\u53CD\u5F39\u5C0F\u7403\uFF0C\u51FB\u788E\u6240\u6709\u7816\u5757\uFF1B\u7403\u843D\u5230\u5E95\u90E8\u6263\u4E00\u6761\u547D\u3002" })
|
|
5887
|
+
] }),
|
|
5888
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dgame-score", children: [
|
|
5889
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
5890
|
+
"\u5F97\u5206 ",
|
|
5891
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: score })
|
|
5892
|
+
] }),
|
|
5893
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
5894
|
+
"\u547D ",
|
|
5895
|
+
lives,
|
|
5896
|
+
" \xB7 \u7B2C ",
|
|
5897
|
+
level,
|
|
5898
|
+
" \u5173"
|
|
5899
|
+
] })
|
|
5900
|
+
] })
|
|
5901
|
+
] }),
|
|
5902
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dgame-breakout", children: [
|
|
5903
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("canvas", { ref: canvasRef, width: W, height: H, tabIndex: 0, onKeyDown, onKeyUp, onPointerMove, onClick: () => focusStage(canvasRef), className: "dgame-breakout-canvas", "aria-label": "\u6253\u7816\u5757\u6E38\u620F\u533A\u57DF\uFF0C\u5DE6\u53F3\u65B9\u5411\u952E\u6216\u9F20\u6807\u79FB\u52A8\u6321\u677F\uFF0C\u7A7A\u683C\u53D1\u7403" }),
|
|
5904
|
+
status === "win" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dgame-over", children: [
|
|
5905
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("strong", { children: [
|
|
5906
|
+
"\u5168\u6D88\uFF01\u8FDB\u5165\u7B2C ",
|
|
5907
|
+
level,
|
|
5908
|
+
" \u5173"
|
|
5909
|
+
] }),
|
|
5910
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => {
|
|
5911
|
+
start(level);
|
|
5912
|
+
setStatus("play");
|
|
5913
|
+
statusRef.current = "play";
|
|
5914
|
+
}, children: "\u4E0B\u4E00\u5173" })
|
|
5915
|
+
] }) : null,
|
|
5916
|
+
status === "over" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dgame-over", children: [
|
|
5917
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("strong", { children: [
|
|
5918
|
+
"\u7403\u6389\u5149\u4E86 \xB7 \u5F97 ",
|
|
5919
|
+
score,
|
|
5920
|
+
" \u5206"
|
|
5921
|
+
] }),
|
|
5922
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => {
|
|
5923
|
+
start(1);
|
|
5924
|
+
setScore(0);
|
|
5925
|
+
setLives(3);
|
|
5926
|
+
setStatus("play");
|
|
5927
|
+
statusRef.current = "play";
|
|
5928
|
+
livesRef.current = 3;
|
|
5929
|
+
}, children: "\u91CD\u65B0\u5F00\u59CB" })
|
|
5930
|
+
] }) : null
|
|
5931
|
+
] }),
|
|
5932
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dgame-controls", children: [
|
|
5933
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "aria-label": "\u5411\u5DE6\u79FB\u52A8", onClick: () => {
|
|
5934
|
+
keys.current.left = false;
|
|
5935
|
+
game.current && (game.current.paddle.x = Math.max(0, game.current.paddle.x - 12));
|
|
5936
|
+
}, children: "\u2190 \u5DE6\u79FB" }),
|
|
5937
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "\u2190/\u2192 \u6216\u9F20\u6807\u79FB\u52A8 \xB7 \u7A7A\u683C\u53D1\u7403" }),
|
|
5938
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { type: "button", "aria-label": "\u5411\u53F3\u79FB\u52A8", onClick: () => {
|
|
5939
|
+
keys.current.right = false;
|
|
5940
|
+
game.current && (game.current.paddle.x = Math.min(W - PADDLE_W, game.current.paddle.x + 12));
|
|
5941
|
+
}, children: [
|
|
5942
|
+
"\u53F3\u79FB ",
|
|
5943
|
+
"->"
|
|
5944
|
+
] })
|
|
5945
|
+
] })
|
|
5946
|
+
] });
|
|
5947
|
+
}
|
|
5948
|
+
function BreakoutPreview() {
|
|
5949
|
+
const cols = ["#f87171", "#fb923c", "#facc15", "#4ade80", "#38bdf8", "#c084fc"];
|
|
5950
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "dgcov-prev dgcov-prev-breakout", "aria-hidden": "true", children: [
|
|
5951
|
+
Array.from({ length: 12 }, (_, i) => {
|
|
5952
|
+
const row = Math.floor(i / 6), c = i % 6;
|
|
5953
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("i", { style: { left: 5 + c * 15 + "%", top: 10 + row * 12 + "%", background: cols[i % cols.length] } }, i);
|
|
5954
|
+
}),
|
|
5955
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("b", {})
|
|
5956
|
+
] });
|
|
5957
|
+
}
|
|
5958
|
+
var breakoutGame = { Game: BreakoutGame, Preview: BreakoutPreview, css: `
|
|
5959
|
+
.dgame-breakout{position:relative;align-self:center;width:min(100%,320px);margin:0 auto}.dgame-breakout-canvas{display:block;width:100%;aspect-ratio:320/360;border:1px solid color-mix(in srgb,rgb(56 189 248) 32%,var(--dsw-alias-border-l1));border-radius:10px;outline:none;background:#0b1024;box-shadow:0 4px 18px rgb(0 0 0/.4)}.dgame-breakout-canvas:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,rgb(56 189 248) 35%,transparent)}.dgame-breakout .dgame-over{position:absolute;inset:0;border-radius:10px}
|
|
5960
|
+
` };
|
|
5961
|
+
|
|
5962
|
+
// features/games/games/racer.jsx
|
|
5963
|
+
var import_react16 = require("react");
|
|
5964
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
5965
|
+
var W2 = 300;
|
|
5966
|
+
var H2 = 420;
|
|
5967
|
+
var ROAD_W = 232;
|
|
5968
|
+
var ROAD_X = (W2 - ROAD_W) / 2;
|
|
5969
|
+
var CAR_W = 34;
|
|
5970
|
+
var CAR_H = 52;
|
|
5971
|
+
var CAR_COLORS = ["#f87171", "#fb923c", "#c084fc", "#38bdf8", "#4ade80", "#fde047"];
|
|
5972
|
+
function laneX(lane) {
|
|
5973
|
+
return ROAD_X + ROAD_W * (lane + 0.5) / 3 - CAR_W / 2;
|
|
5974
|
+
}
|
|
5975
|
+
function overlap(a, b) {
|
|
5976
|
+
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
|
|
5977
|
+
}
|
|
5978
|
+
function rect(ctx, x, y, w, h, r, fill) {
|
|
5979
|
+
ctx.fillStyle = fill;
|
|
5980
|
+
ctx.beginPath();
|
|
5981
|
+
ctx.moveTo(x + r, y);
|
|
5982
|
+
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
5983
|
+
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
5984
|
+
ctx.arcTo(x, y + h, x, y, r);
|
|
5985
|
+
ctx.arcTo(x, y, x + w, y, r);
|
|
5986
|
+
ctx.closePath();
|
|
5987
|
+
ctx.fill();
|
|
5988
|
+
}
|
|
5989
|
+
function RacerGame(props) {
|
|
5990
|
+
const canvasRef = (0, import_react16.useRef)(null);
|
|
5991
|
+
const [status, setStatus] = (0, import_react16.useState)("play");
|
|
5992
|
+
const game = (0, import_react16.useRef)(null);
|
|
5993
|
+
const keys = (0, import_react16.useRef)({});
|
|
5994
|
+
const statusRef = (0, import_react16.useRef)("play");
|
|
5995
|
+
statusRef.current = status;
|
|
5996
|
+
const pausedRef = (0, import_react16.useRef)(!!(props && props.paused));
|
|
5997
|
+
pausedRef.current = !!(props && props.paused);
|
|
5998
|
+
const reset = (0, import_react16.useCallback)(() => {
|
|
5999
|
+
game.current = {
|
|
6000
|
+
stars: Array.from({ length: 50 }, () => ({ x: Math.random() * W2, y: Math.random() * H2, a: 0.2 + Math.random() * 0.5 })),
|
|
6001
|
+
player: { x: laneX(1), y: H2 - CAR_H - 16 },
|
|
6002
|
+
enemies: [],
|
|
6003
|
+
coins: [],
|
|
6004
|
+
dist: 0,
|
|
6005
|
+
coinsGot: 0,
|
|
6006
|
+
spawn: 0,
|
|
6007
|
+
coinSpawn: 0,
|
|
6008
|
+
speed: 3.4,
|
|
6009
|
+
over: false
|
|
6010
|
+
};
|
|
6011
|
+
}, []);
|
|
6012
|
+
(0, import_react16.useEffect)(() => {
|
|
6013
|
+
reset();
|
|
6014
|
+
setStatus("play");
|
|
6015
|
+
}, [reset]);
|
|
6016
|
+
const launch = (0, import_react16.useCallback)(() => {
|
|
6017
|
+
reset();
|
|
6018
|
+
setStatus("play");
|
|
6019
|
+
}, [reset]);
|
|
6020
|
+
(0, import_react16.useEffect)(() => {
|
|
6021
|
+
const draw2 = () => {
|
|
6022
|
+
const g = game.current, ctx = canvasRef.current && canvasRef.current.getContext("2d");
|
|
6023
|
+
if (!g || !ctx) return;
|
|
6024
|
+
ctx.fillStyle = "#0b1024";
|
|
6025
|
+
ctx.fillRect(0, 0, W2, H2);
|
|
6026
|
+
ctx.fillStyle = "#1b2bb0";
|
|
6027
|
+
for (const s of g.stars) {
|
|
6028
|
+
ctx.globalAlpha = s.a;
|
|
6029
|
+
ctx.fillRect(s.x, s.y, 1.5, 1.5);
|
|
6030
|
+
}
|
|
6031
|
+
ctx.globalAlpha = 1;
|
|
6032
|
+
ctx.fillStyle = "#20243a";
|
|
6033
|
+
ctx.fillRect(ROAD_X, 0, ROAD_W, H2);
|
|
6034
|
+
ctx.fillStyle = "rgba(255,255,255,.09)";
|
|
6035
|
+
ctx.fillRect(ROAD_X + 6, 0, 4, H2);
|
|
6036
|
+
ctx.fillRect(ROAD_X + ROAD_W - 10, 0, 4, H2);
|
|
6037
|
+
ctx.fillStyle = "rgba(255,255,255,.16)";
|
|
6038
|
+
for (let i = 1; i < 3; i += 1) {
|
|
6039
|
+
const lx = ROAD_X + ROAD_W * i / 3 - 1;
|
|
6040
|
+
for (let yy = g.dist % 60; yy < H2; yy += 60) ctx.fillRect(lx, yy, 2, 26);
|
|
6041
|
+
}
|
|
6042
|
+
if (pausedRef.current || g.over) {
|
|
6043
|
+
drawHUD(ctx, g);
|
|
6044
|
+
return;
|
|
6045
|
+
}
|
|
6046
|
+
if (keys.current.left) g.player.x -= 5;
|
|
6047
|
+
if (keys.current.right) g.player.x += 5;
|
|
6048
|
+
g.player.x = Math.max(ROAD_X + 2, Math.min(ROAD_X + ROAD_W - CAR_W - 2, g.player.x));
|
|
6049
|
+
g.dist += g.speed * 0.16;
|
|
6050
|
+
g.spawn += 1;
|
|
6051
|
+
if (g.spawn > Math.max(20, 56 - g.dist / 600)) {
|
|
6052
|
+
g.spawn = 0;
|
|
6053
|
+
const lane = Math.floor(Math.random() * 3);
|
|
6054
|
+
const v = g.speed * (1.1 + Math.random() * 0.6);
|
|
6055
|
+
g.enemies.push({ x: laneX(lane), y: -CAR_H - Math.random() * 60, w: CAR_W, h: CAR_H, v, c: CAR_COLORS[Math.floor(Math.random() * CAR_COLORS.length)] });
|
|
6056
|
+
}
|
|
6057
|
+
g.coinSpawn += 1;
|
|
6058
|
+
if (g.coinSpawn > Math.max(30, 70 - g.dist / 500)) {
|
|
6059
|
+
g.coinSpawn = 0;
|
|
6060
|
+
g.coins.push({ x: laneX(Math.floor(Math.random() * 3)), y: -30, r: 11, got: false });
|
|
6061
|
+
}
|
|
6062
|
+
for (const e of g.enemies) e.y += e.v;
|
|
6063
|
+
for (const c of g.coins) c.y += g.speed;
|
|
6064
|
+
g.enemies = g.enemies.filter((e) => e.y < H2 + 20);
|
|
6065
|
+
g.coins = g.coins.filter((c) => c.y < H2 + 20);
|
|
6066
|
+
for (const e of g.enemies) {
|
|
6067
|
+
if (overlap(g.player, e)) {
|
|
6068
|
+
g.over = true;
|
|
6069
|
+
setStatus("over");
|
|
6070
|
+
statusRef.current = "over";
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
for (const c of g.coins) {
|
|
6074
|
+
if (!c.got && Math.abs(c.x + c.r - (g.player.x + CAR_W / 2)) < CAR_W / 2 + 8 && Math.abs(c.y - (g.player.y + CAR_H / 2)) < CAR_H / 2 + 10) {
|
|
6075
|
+
c.got = true;
|
|
6076
|
+
g.coinsGot += 1;
|
|
6077
|
+
}
|
|
6078
|
+
}
|
|
6079
|
+
for (const e of g.enemies) {
|
|
6080
|
+
rect(ctx, e.x, e.y, e.w, e.h, 7, e.c);
|
|
6081
|
+
ctx.fillStyle = "rgba(255,255,255,.25)";
|
|
6082
|
+
ctx.fillRect(e.x + 6, e.y + 7, e.w - 12, 12);
|
|
6083
|
+
}
|
|
6084
|
+
ctx.fillStyle = "#fde047";
|
|
6085
|
+
for (const c of g.coins) {
|
|
6086
|
+
ctx.beginPath();
|
|
6087
|
+
ctx.arc(c.x + c.r, c.y, c.r, 0, Math.PI * 2);
|
|
6088
|
+
ctx.fill();
|
|
6089
|
+
ctx.fillStyle = "rgba(120,53,15,.4)";
|
|
6090
|
+
ctx.beginPath();
|
|
6091
|
+
ctx.arc(c.x + c.r, c.y, c.r * 0.5, 0, Math.PI * 2);
|
|
6092
|
+
ctx.fill();
|
|
6093
|
+
ctx.fillStyle = "#fde047";
|
|
6094
|
+
}
|
|
6095
|
+
drawCar(ctx, g.player.x, g.player.y, "#22d3ee");
|
|
6096
|
+
drawHUD(ctx, g);
|
|
6097
|
+
};
|
|
6098
|
+
const drawHUD = (ctx, g) => {
|
|
6099
|
+
ctx.fillStyle = "#e7ecff";
|
|
6100
|
+
ctx.font = "bold 13px system-ui, sans-serif";
|
|
6101
|
+
ctx.textBaseline = "top";
|
|
6102
|
+
ctx.fillText("\u8DDD\u79BB " + Math.floor(g.dist / 60) + " m", 10, 10);
|
|
6103
|
+
ctx.fillStyle = "#fde047";
|
|
6104
|
+
ctx.fillText("\u91D1\u5E01 \xD7 " + g.coinsGot, W2 - 70, 10);
|
|
6105
|
+
};
|
|
6106
|
+
function drawCar(ctx, x, y, c) {
|
|
6107
|
+
rect(ctx, x, y, CAR_W, CAR_H, 9, c);
|
|
6108
|
+
ctx.fillStyle = "rgba(255,255,255,.3)";
|
|
6109
|
+
rect(ctx, x + 5, y + 6, CAR_W - 10, 13, 4, "rgba(255,255,255,.3)");
|
|
6110
|
+
ctx.fillStyle = "rgba(15,23,42,.5)";
|
|
6111
|
+
rect(ctx, x + 5, y + CAR_H - 16, CAR_W - 10, 9, 3, "rgba(15,23,42,.5)");
|
|
6112
|
+
ctx.fillStyle = "rgba(255,255,255,.45)";
|
|
6113
|
+
rect(ctx, x + 3, y + CAR_H - 4, 6, 5, 2, "#fde047");
|
|
6114
|
+
rect(ctx, x + CAR_W - 9, y + CAR_H - 4, 6, 5, 2, "#fde047");
|
|
6115
|
+
}
|
|
6116
|
+
let raf = requestAnimationFrame(function loop() {
|
|
6117
|
+
draw2();
|
|
6118
|
+
raf = requestAnimationFrame(loop);
|
|
6119
|
+
});
|
|
6120
|
+
return () => cancelAnimationFrame(raf);
|
|
6121
|
+
}, []);
|
|
6122
|
+
const onKeyDown = (0, import_react16.useCallback)((event) => {
|
|
6123
|
+
if (pausedRef.current) return;
|
|
6124
|
+
if (event.key === "ArrowLeft") {
|
|
6125
|
+
event.preventDefault();
|
|
6126
|
+
keys.current.left = true;
|
|
6127
|
+
} else if (event.key === "ArrowRight") {
|
|
6128
|
+
event.preventDefault();
|
|
6129
|
+
keys.current.right = true;
|
|
6130
|
+
} else if (event.key.toLowerCase() === "r") {
|
|
6131
|
+
event.preventDefault();
|
|
6132
|
+
launch();
|
|
6133
|
+
}
|
|
6134
|
+
}, [launch]);
|
|
6135
|
+
const onKeyUp = (0, import_react16.useCallback)((event) => {
|
|
6136
|
+
if (event.key === "ArrowLeft") keys.current.left = false;
|
|
6137
|
+
else if (event.key === "ArrowRight") keys.current.right = false;
|
|
6138
|
+
}, []);
|
|
6139
|
+
const onPointerMove = (0, import_react16.useCallback)((e) => {
|
|
6140
|
+
const g = game.current, cv = canvasRef.current;
|
|
6141
|
+
if (!g || !cv) return;
|
|
6142
|
+
const rect0 = cv.getBoundingClientRect();
|
|
6143
|
+
const scale = rect0.width / W2;
|
|
6144
|
+
g.player.x = Math.max(ROAD_X + 2, Math.min(ROAD_X + ROAD_W - CAR_W - 2, (e.clientX - rect0.left) / scale - CAR_W / 2));
|
|
6145
|
+
}, []);
|
|
6146
|
+
useGameControls(canvasRef, pausedRef.current, onKeyDown, onKeyUp);
|
|
6147
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "dgame-game", "aria-label": "\u6781\u901F\u8D5B\u8F66", children: [
|
|
6148
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dgame-game-head", children: [
|
|
6149
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
|
|
6150
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { children: "\u6781\u901F\u8D5B\u8F66" }),
|
|
6151
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { children: "\u5DE6\u53F3\u79FB\u52A8\u907F\u5F00\u8FCE\u9762\u6765\u8F66\u3001\u6536\u96C6\u91D1\u5E01\uFF0C\u8D8A\u5F00\u8D8A\u5FEB\uFF1B\u649E\u8F66\u5373\u7ED3\u675F\u3002\u8DDD\u79BB\u4E0E\u91D1\u5E01\u5B9E\u65F6\u663E\u793A\u5728\u6E38\u620F\u753B\u9762\u5DE6\u4E0A/\u53F3\u4E0A\u3002" })
|
|
6152
|
+
] }),
|
|
6153
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dgame-score", children: [
|
|
6154
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "\u64CD\u4F5C" }),
|
|
6155
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "\u2190/\u2192 \u6216\u9F20\u6807" })
|
|
6156
|
+
] })
|
|
6157
|
+
] }),
|
|
6158
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dgame-racer", children: [
|
|
6159
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("canvas", { ref: canvasRef, width: W2, height: H2, tabIndex: 0, onKeyDown, onKeyUp, onPointerMove, onClick: () => focusStage(canvasRef), className: "dgame-racer-canvas", "aria-label": "\u6781\u901F\u8D5B\u8F66\u6E38\u620F\u533A\u57DF\uFF0C\u5DE6\u53F3\u65B9\u5411\u952E\u6216\u9F20\u6807\u63A7\u5236\u65B9\u5411" }),
|
|
6160
|
+
status === "over" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dgame-over", children: [
|
|
6161
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("strong", { children: [
|
|
6162
|
+
"\u649E\u8F66\u4E86 \xB7 \u884C\u9A76 ",
|
|
6163
|
+
game.current && Math.floor(game.current.dist / 60) || 0,
|
|
6164
|
+
" \u7C73"
|
|
6165
|
+
] }),
|
|
6166
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: launch, children: "\u91CD\u65B0\u51FA\u53D1" })
|
|
6167
|
+
] }) : null
|
|
6168
|
+
] }),
|
|
6169
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dgame-controls", children: [
|
|
6170
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", "aria-label": "\u5411\u5DE6", onClick: () => {
|
|
6171
|
+
keys.current.left = false;
|
|
6172
|
+
if (game.current) game.current.player.x = Math.max(ROAD_X + 2, game.current.player.x - 14);
|
|
6173
|
+
}, children: "\u2190 \u5DE6\u79FB" }),
|
|
6174
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "\u2190/\u2192 \u6216\u9F20\u6807\u79FB\u52A8 \xB7 R \u91CD\u5F00" }),
|
|
6175
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("button", { type: "button", "aria-label": "\u5411\u53F3", onClick: () => {
|
|
6176
|
+
keys.current.right = false;
|
|
6177
|
+
if (game.current) game.current.player.x = Math.min(ROAD_X + ROAD_W - CAR_W - 2, game.current.player.x + 14);
|
|
6178
|
+
}, children: [
|
|
6179
|
+
"\u53F3\u79FB ",
|
|
6180
|
+
"->"
|
|
6181
|
+
] })
|
|
6182
|
+
] })
|
|
6183
|
+
] });
|
|
6184
|
+
}
|
|
6185
|
+
function RacerPreview() {
|
|
6186
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "dgcov-prev dgcov-prev-racer", "aria-hidden": "true", children: [
|
|
6187
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", { style: { left: "20%", top: "8%" } }),
|
|
6188
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", { style: { left: "55%", top: "22%" } }),
|
|
6189
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", { style: { left: "20%", top: "42%" } }),
|
|
6190
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", { style: { left: "64%", top: "58%" } }),
|
|
6191
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("i", { style: { left: "38%", top: "74%" } }),
|
|
6192
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("b", {})
|
|
6193
|
+
] });
|
|
6194
|
+
}
|
|
6195
|
+
var racerGame = { Game: RacerGame, Preview: RacerPreview, css: `
|
|
6196
|
+
.dgame-racer{position:relative;align-self:center;width:min(100%,300px);margin:0 auto}.dgame-racer-canvas{display:block;width:100%;aspect-ratio:300/420;border:1px solid color-mix(in srgb,rgb(34 211 238) 32%,var(--dsw-alias-border-l1));border-radius:10px;outline:none;background:#0b1024;box-shadow:0 4px 18px rgb(0 0 0/.4)}.dgame-racer-canvas:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,rgb(34 211 238) 35%,transparent)}.dgame-racer .dgame-over{position:absolute;inset:0;border-radius:10px}.dgame-racer-stat{color:var(--dsw-alias-label-tertiary)}
|
|
6197
|
+
` };
|
|
6198
|
+
|
|
6199
|
+
// features/games/games/tank.jsx
|
|
6200
|
+
var import_react17 = require("react");
|
|
6201
|
+
|
|
6202
|
+
// features/games/tank-core.js
|
|
6203
|
+
var GRID = 15;
|
|
6204
|
+
var EMPTY = 0;
|
|
6205
|
+
var STEEL = 1;
|
|
6206
|
+
var BRICK = 2;
|
|
6207
|
+
var BASE = 3;
|
|
6208
|
+
var DIRS4 = { up: [-1, 0], down: [1, 0], left: [0, -1], right: [0, 1] };
|
|
6209
|
+
var DIR_KEYS = Object.keys(DIRS4);
|
|
6210
|
+
var SPAWNS = [{ r: 0, c: 0 }, { r: 0, c: 7 }, { r: 0, c: 14 }];
|
|
6211
|
+
function cloneGrid2(g) {
|
|
6212
|
+
return g.map((row) => row.slice());
|
|
6213
|
+
}
|
|
6214
|
+
function inBounds2(r, c) {
|
|
6215
|
+
return r >= 0 && r < GRID && c >= 0 && c < GRID;
|
|
6216
|
+
}
|
|
6217
|
+
function cellBlockedForTank(grid, r, c) {
|
|
6218
|
+
return !inBounds2(r, c) || grid[r][c] !== EMPTY;
|
|
6219
|
+
}
|
|
6220
|
+
function makeGrid() {
|
|
6221
|
+
const g = Array.from({ length: GRID }, () => Array(GRID).fill(EMPTY));
|
|
6222
|
+
const set = (r, c, v) => {
|
|
6223
|
+
if (r >= 0 && r < GRID && c >= 0 && c < GRID) g[r][c] = v;
|
|
6224
|
+
};
|
|
6225
|
+
for (let r = 2; r <= 5; r += 1) {
|
|
6226
|
+
set(r, 3, STEEL);
|
|
6227
|
+
set(r, 11, STEEL);
|
|
6228
|
+
set(r, 7, STEEL);
|
|
6229
|
+
}
|
|
6230
|
+
for (let c = 4; c <= 10; c += 1) set(8, c, BRICK);
|
|
6231
|
+
set(9, 5, BRICK);
|
|
6232
|
+
set(9, 6, BRICK);
|
|
6233
|
+
set(9, 8, BRICK);
|
|
6234
|
+
set(9, 9, BRICK);
|
|
6235
|
+
set(9, 10, BRICK);
|
|
6236
|
+
set(13, 7, BASE);
|
|
6237
|
+
set(12, 6, BRICK);
|
|
6238
|
+
set(12, 7, BRICK);
|
|
6239
|
+
set(12, 8, BRICK);
|
|
6240
|
+
set(13, 6, BRICK);
|
|
6241
|
+
set(13, 8, BRICK);
|
|
6242
|
+
return g;
|
|
6243
|
+
}
|
|
6244
|
+
function findBase(grid) {
|
|
6245
|
+
for (let r = 0; r < GRID; r += 1) for (let c = 0; c < GRID; c += 1) if (grid[r][c] === BASE) return { r, c };
|
|
6246
|
+
return { r: 13, c: 7 };
|
|
6247
|
+
}
|
|
6248
|
+
function playerSpawn() {
|
|
6249
|
+
return { r: 11, c: 7, dir: "up", alive: true, lives: 3, cooldown: 0 };
|
|
6250
|
+
}
|
|
6251
|
+
function initialState() {
|
|
6252
|
+
return {
|
|
6253
|
+
grid: makeGrid(),
|
|
6254
|
+
player: playerSpawn(),
|
|
6255
|
+
enemies: [],
|
|
6256
|
+
bullets: [],
|
|
6257
|
+
score: 0,
|
|
6258
|
+
wave: 1,
|
|
6259
|
+
remaining: 4,
|
|
6260
|
+
active: 2,
|
|
6261
|
+
spawnTick: 0,
|
|
6262
|
+
over: false,
|
|
6263
|
+
win: false,
|
|
6264
|
+
respawnTicks: 0
|
|
6265
|
+
};
|
|
6266
|
+
}
|
|
6267
|
+
function spawnBullet(r, c, dir, from) {
|
|
6268
|
+
const [dr, dc] = DIRS4[dir];
|
|
6269
|
+
return { r: r + dr, c: c + dc, dr, dc, from };
|
|
6270
|
+
}
|
|
6271
|
+
function spawnOne(grid, enemies) {
|
|
6272
|
+
for (const s of SPAWNS) {
|
|
6273
|
+
if (grid[s.r][s.c] === EMPTY && !enemies.some((e) => e.r === s.r && e.c === s.c)) {
|
|
6274
|
+
return { r: s.r, c: s.c, dir: "down", hp: 1, cooldown: 3, moveTick: 0 };
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
return null;
|
|
6278
|
+
}
|
|
6279
|
+
function step3(state, input) {
|
|
6280
|
+
if (state.over || state.win) return state;
|
|
6281
|
+
const grid = cloneGrid2(state.grid);
|
|
6282
|
+
let player = Object.assign({}, state.player);
|
|
6283
|
+
let enemies = state.enemies.map((e) => Object.assign({}, e));
|
|
6284
|
+
let bullets = state.bullets.map((b) => Object.assign({}, b));
|
|
6285
|
+
let remaining = state.remaining, score = state.score, over = state.over;
|
|
6286
|
+
let respawnTicks = state.respawnTicks;
|
|
6287
|
+
let spawnTick = state.spawnTick + 1;
|
|
6288
|
+
if (player.alive && input.dir && DIRS4[input.dir]) {
|
|
6289
|
+
const [dr, dc] = DIRS4[input.dir];
|
|
6290
|
+
player.dir = input.dir;
|
|
6291
|
+
const nr = player.r + dr, nc = player.c + dc;
|
|
6292
|
+
if (!cellBlockedForTank(grid, nr, nc)) {
|
|
6293
|
+
player.r = nr;
|
|
6294
|
+
player.c = nc;
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
if (player.alive && input.shoot && player.cooldown <= 0) {
|
|
6298
|
+
bullets.push(spawnBullet(player.r, player.c, player.dir, "p"));
|
|
6299
|
+
player.cooldown = 9;
|
|
6300
|
+
}
|
|
6301
|
+
player.cooldown = Math.max(0, player.cooldown - 1);
|
|
6302
|
+
for (let i = 0; i < enemies.length; i += 1) {
|
|
6303
|
+
const e = enemies[i];
|
|
6304
|
+
e.cooldown -= 1;
|
|
6305
|
+
e.moveTick += 1;
|
|
6306
|
+
if (e.moveTick % 12 === 0) {
|
|
6307
|
+
const [dr, dc] = DIRS4[e.dir];
|
|
6308
|
+
const nr = e.r + dr, nc = e.c + dc;
|
|
6309
|
+
if (cellBlockedForTank(grid, nr, nc) || enemies.some((o, oi) => oi !== i && o.r === nr && o.c === nc)) {
|
|
6310
|
+
const opts = DIR_KEYS.filter((d) => {
|
|
6311
|
+
const [ddr, ddc] = DIRS4[d];
|
|
6312
|
+
const tr = e.r + ddr, tc = e.c + ddc;
|
|
6313
|
+
return !cellBlockedForTank(grid, tr, tc) && !enemies.some((o, oi) => oi !== i && o.r === tr && o.c === tc);
|
|
6314
|
+
});
|
|
6315
|
+
if (opts.length) e.dir = opts[Math.floor(Math.random() * opts.length)];
|
|
6316
|
+
} else {
|
|
6317
|
+
e.r = nr;
|
|
6318
|
+
e.c = nc;
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
if (e.cooldown <= 0) {
|
|
6322
|
+
bullets.push(spawnBullet(e.r, e.c, e.dir, "e"));
|
|
6323
|
+
e.cooldown = 9 + Math.floor(Math.random() * 9);
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
let baseHit = false, playerHit = false;
|
|
6327
|
+
const nextBullets = [];
|
|
6328
|
+
const base = findBase(grid);
|
|
6329
|
+
for (const b of bullets) {
|
|
6330
|
+
const nr = b.r + b.dr, nc = b.c + b.dc;
|
|
6331
|
+
if (!inBounds2(nr, nc)) continue;
|
|
6332
|
+
const cell = grid[nr][nc];
|
|
6333
|
+
if (cell === STEEL) continue;
|
|
6334
|
+
if (cell === BRICK) {
|
|
6335
|
+
grid[nr][nc] = EMPTY;
|
|
6336
|
+
continue;
|
|
6337
|
+
}
|
|
6338
|
+
if (cell === BASE) {
|
|
6339
|
+
if (b.from === "e") baseHit = true;
|
|
6340
|
+
continue;
|
|
6341
|
+
}
|
|
6342
|
+
if (b.from === "p") {
|
|
6343
|
+
const idx = enemies.findIndex((e) => e.r === nr && e.c === nc);
|
|
6344
|
+
if (idx >= 0) {
|
|
6345
|
+
enemies.splice(idx, 1);
|
|
6346
|
+
score += 100;
|
|
6347
|
+
continue;
|
|
6348
|
+
}
|
|
6349
|
+
} else if (player.alive && player.r === nr && player.c === nc) {
|
|
6350
|
+
player.lives -= 1;
|
|
6351
|
+
if (player.lives <= 0) over = true;
|
|
6352
|
+
else {
|
|
6353
|
+
player.alive = false;
|
|
6354
|
+
respawnTicks = 24;
|
|
6355
|
+
}
|
|
6356
|
+
continue;
|
|
6357
|
+
}
|
|
6358
|
+
b.r = nr;
|
|
6359
|
+
b.c = nc;
|
|
6360
|
+
nextBullets.push(b);
|
|
6361
|
+
}
|
|
6362
|
+
if (remaining > 0 && enemies.length < state.active && spawnTick % 9 === 0) {
|
|
6363
|
+
const fresh = spawnOne(grid, enemies);
|
|
6364
|
+
if (fresh) {
|
|
6365
|
+
enemies.push(fresh);
|
|
6366
|
+
remaining -= 1;
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
if (!player.alive && !over) {
|
|
6370
|
+
if (respawnTicks <= 0) {
|
|
6371
|
+
player.alive = true;
|
|
6372
|
+
player.r = 11;
|
|
6373
|
+
player.c = 7;
|
|
6374
|
+
player.dir = "up";
|
|
6375
|
+
player.cooldown = 0;
|
|
6376
|
+
} else respawnTicks -= 1;
|
|
6377
|
+
}
|
|
6378
|
+
const win = remaining <= 0 && enemies.length === 0;
|
|
6379
|
+
return Object.assign({}, state, {
|
|
6380
|
+
grid,
|
|
6381
|
+
player,
|
|
6382
|
+
enemies,
|
|
6383
|
+
bullets: nextBullets,
|
|
6384
|
+
score,
|
|
6385
|
+
remaining,
|
|
6386
|
+
over: over || baseHit,
|
|
6387
|
+
win,
|
|
6388
|
+
spawnTick,
|
|
6389
|
+
respawnTicks
|
|
6390
|
+
});
|
|
6391
|
+
}
|
|
6392
|
+
function nextWave(state) {
|
|
6393
|
+
return Object.assign({}, state, {
|
|
6394
|
+
wave: state.wave + 1,
|
|
6395
|
+
remaining: Math.min(3 + state.wave, 8),
|
|
6396
|
+
active: Math.min(2 + Math.floor(state.wave / 2), 4),
|
|
6397
|
+
enemies: [],
|
|
6398
|
+
win: false,
|
|
6399
|
+
spawnTick: 0
|
|
6400
|
+
});
|
|
6401
|
+
}
|
|
6402
|
+
|
|
6403
|
+
// features/games/games/tank.jsx
|
|
6404
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
6405
|
+
var DIR_ANGLE = { up: 0, right: 90, down: 180, left: 270 };
|
|
6406
|
+
var ENEMY_COLORS = ["#f87171", "#fb923c", "#c084fc", "#4ade80", "#38bdf8"];
|
|
6407
|
+
function TankGame(props) {
|
|
6408
|
+
const [snap, setSnap] = (0, import_react17.useState)(initialState);
|
|
6409
|
+
const [status, setStatus] = (0, import_react17.useState)("play");
|
|
6410
|
+
const snapRef = (0, import_react17.useRef)(snap);
|
|
6411
|
+
snapRef.current = snap;
|
|
6412
|
+
const keys = (0, import_react17.useRef)({ dir: null, shoot: false });
|
|
6413
|
+
const pausedRef = (0, import_react17.useRef)(!!(props && props.paused));
|
|
6414
|
+
pausedRef.current = !!(props && props.paused);
|
|
6415
|
+
const statusRef = (0, import_react17.useRef)("play");
|
|
6416
|
+
statusRef.current = status;
|
|
6417
|
+
const boardRef = (0, import_react17.useRef)(null);
|
|
6418
|
+
const start = (0, import_react17.useCallback)(() => {
|
|
6419
|
+
setSnap(initialState());
|
|
6420
|
+
setStatus("play");
|
|
6421
|
+
statusRef.current = "play";
|
|
6422
|
+
}, []);
|
|
6423
|
+
const nextLvl = (0, import_react17.useCallback)(() => {
|
|
6424
|
+
setSnap((s) => nextWave(s));
|
|
6425
|
+
setStatus("play");
|
|
6426
|
+
statusRef.current = "play";
|
|
6427
|
+
}, []);
|
|
6428
|
+
(0, import_react17.useEffect)(() => {
|
|
6429
|
+
const timer = setInterval(() => {
|
|
6430
|
+
if (pausedRef.current) return;
|
|
6431
|
+
setSnap((s) => {
|
|
6432
|
+
if (s.over || s.win) return s;
|
|
6433
|
+
const input = { dir: keys.current.dir, shoot: keys.current.shoot };
|
|
6434
|
+
const ns = step3(s, input);
|
|
6435
|
+
if (ns.over && statusRef.current === "play") {
|
|
6436
|
+
statusRef.current = "over";
|
|
6437
|
+
setStatus("over");
|
|
6438
|
+
} else if (ns.win && statusRef.current === "play") {
|
|
6439
|
+
statusRef.current = "wave";
|
|
6440
|
+
setStatus("wave");
|
|
6441
|
+
}
|
|
6442
|
+
return ns;
|
|
6443
|
+
});
|
|
6444
|
+
}, 120);
|
|
6445
|
+
return () => clearInterval(timer);
|
|
6446
|
+
}, []);
|
|
6447
|
+
const stageRef = (0, import_react17.useRef)(null);
|
|
6448
|
+
const onKeyDown = (0, import_react17.useCallback)((event) => {
|
|
6449
|
+
if (pausedRef.current) return;
|
|
6450
|
+
const map = { ArrowUp: "up", ArrowDown: "down", ArrowLeft: "left", ArrowRight: "right", w: "up", s: "down", a: "left", d: "right", W: "up", S: "down", A: "left", D: "right" };
|
|
6451
|
+
const dir = map[event.key];
|
|
6452
|
+
if (dir) {
|
|
6453
|
+
event.preventDefault();
|
|
6454
|
+
keys.current.dir = dir;
|
|
6455
|
+
return;
|
|
6456
|
+
}
|
|
6457
|
+
if (event.key === " ") {
|
|
6458
|
+
event.preventDefault();
|
|
6459
|
+
keys.current.shoot = true;
|
|
6460
|
+
} else if (event.key.toLowerCase() === "r") {
|
|
6461
|
+
event.preventDefault();
|
|
6462
|
+
start();
|
|
6463
|
+
}
|
|
6464
|
+
}, [start]);
|
|
6465
|
+
const onKeyUp = (0, import_react17.useCallback)((event) => {
|
|
6466
|
+
const map = { ArrowUp: "up", ArrowDown: "down", ArrowLeft: "left", ArrowRight: "right", w: "up", s: "down", a: "left", d: "right", W: "up", S: "down", A: "left", D: "right" };
|
|
6467
|
+
const dir = map[event.key];
|
|
6468
|
+
if (dir && keys.current.dir === dir) keys.current.dir = null;
|
|
6469
|
+
if (event.key === " ") keys.current.shoot = false;
|
|
6470
|
+
}, []);
|
|
6471
|
+
useGameControls(stageRef, pausedRef.current, onKeyDown, onKeyUp);
|
|
6472
|
+
const grid = snap.grid;
|
|
6473
|
+
const player = snap.player;
|
|
6474
|
+
const enemies = snap.enemies;
|
|
6475
|
+
const bullets = snap.bullets;
|
|
6476
|
+
let playerPos = null;
|
|
6477
|
+
if (player && player.alive) playerPos = { r: player.r, c: player.c };
|
|
6478
|
+
const enemyMap = {};
|
|
6479
|
+
for (const e of enemies) enemyMap[e.r + "-" + e.c] = e;
|
|
6480
|
+
const bulletMap = {};
|
|
6481
|
+
for (const b of bullets) bulletMap[b.r + "-" + b.c] = b;
|
|
6482
|
+
const basePos = findBase(grid);
|
|
6483
|
+
const cells = [];
|
|
6484
|
+
for (let r = 0; r < GRID; r += 1) {
|
|
6485
|
+
for (let c = 0; c < GRID; c += 1) {
|
|
6486
|
+
const key = r + "-" + c;
|
|
6487
|
+
let cls = "dgame-tank-cell";
|
|
6488
|
+
let inner = null;
|
|
6489
|
+
if (grid[r][c] === STEEL) cls += " steel";
|
|
6490
|
+
else if (grid[r][c] === BRICK) cls += " brick";
|
|
6491
|
+
else if (grid[r][c] === BASE) cls += " base";
|
|
6492
|
+
if (playerPos && playerPos.r === r && playerPos.c === c) {
|
|
6493
|
+
cls += " pw";
|
|
6494
|
+
inner = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("i", { className: "dgame-tank-body pw", style: { transform: "rotate(" + DIR_ANGLE[player.dir] + "deg)" } });
|
|
6495
|
+
} else if (enemyMap[key]) {
|
|
6496
|
+
const e = enemyMap[key];
|
|
6497
|
+
cls += " en";
|
|
6498
|
+
inner = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("i", { className: "dgame-tank-body en", style: { background: ENEMY_COLORS[(e.r + e.c) % ENEMY_COLORS.length], transform: "rotate(" + DIR_ANGLE[e.dir] + "deg)" } });
|
|
6499
|
+
}
|
|
6500
|
+
if (bullets && bulletMap[key]) cls += " bullet";
|
|
6501
|
+
cells.push(/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: cls, children: [
|
|
6502
|
+
inner,
|
|
6503
|
+
bulletMap[key] ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("i", { className: "dgame-tank-bullet", "aria-hidden": "true" }) : null,
|
|
6504
|
+
grid[r][c] === BASE ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("b", { className: "dgame-tank-flag", "aria-hidden": "true", children: "\u2691" }) : null
|
|
6505
|
+
] }, key));
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
const statusText = status === "over" ? "\u57FA\u5730\u5931\u5B88 \xB7 \u6E38\u620F\u7ED3\u675F" : status === "wave" ? "\u7B2C " + snap.wave + " \u6CE2\u6E05\u7A7A\uFF01\u8FDB\u5165\u4E0B\u4E00\u6CE2" : "\u7B2C " + snap.wave + " \u6CE2 \xB7 \u5F85\u6B7C\u706D " + (snap.remaining + snap.enemies.length) + " \u8F86 \xB7 \u751F\u547D \xD7 " + (player && player.lives);
|
|
6509
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "dgame-game", "aria-label": "\u5766\u514B\u5927\u6218", children: [
|
|
6510
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "dgame-game-head", children: [
|
|
6511
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
|
|
6512
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { children: "\u5766\u514B\u5927\u6218" }),
|
|
6513
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: "\u65B9\u5411\u952E\u79FB\u52A8\uFF0C\u7A7A\u683C\u5F00\u70AE\uFF1B\u51FB\u6BC1\u654C\u65B9\u5766\u514B\uFF0C\u522B\u8BA9\u654C\u519B\u6467\u6BC1\u4E2D\u592E\u57FA\u5730\u3002" })
|
|
6514
|
+
] }),
|
|
6515
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "dgame-score", children: [
|
|
6516
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
|
|
6517
|
+
"\u5F97\u5206 ",
|
|
6518
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: snap.score })
|
|
6519
|
+
] }),
|
|
6520
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
|
|
6521
|
+
"\u751F\u547D \xD7 ",
|
|
6522
|
+
player && player.lives
|
|
6523
|
+
] })
|
|
6524
|
+
] })
|
|
6525
|
+
] }),
|
|
6526
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "dgame-tank-stage", ref: stageRef, tabIndex: 0, role: "grid", onKeyDown, onKeyUp, onClick: () => focusStage(stageRef), "aria-label": "\u5766\u514B\u5927\u6218\u6E38\u620F\u533A\u57DF\uFF0C\u65B9\u5411\u952E\u79FB\u52A8\uFF0C\u7A7A\u683C\u5F00\u70AE", children: [
|
|
6527
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "dgame-tank-grid", children: cells }),
|
|
6528
|
+
status === "over" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "dgame-over", children: [
|
|
6529
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("strong", { children: [
|
|
6530
|
+
"\u80DC\u8D25\u5DF2\u5206 \xB7 \u5F97 ",
|
|
6531
|
+
snap.score,
|
|
6532
|
+
" \u5206"
|
|
6533
|
+
] }),
|
|
6534
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: start, children: "\u91CD\u65B0\u5F00\u59CB" })
|
|
6535
|
+
] }) : null,
|
|
6536
|
+
status === "wave" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "dgame-over", children: [
|
|
6537
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("strong", { children: [
|
|
6538
|
+
"\u7B2C ",
|
|
6539
|
+
snap.wave,
|
|
6540
|
+
" \u6CE2\u6E05\u7A7A\uFF01"
|
|
6541
|
+
] }),
|
|
6542
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: nextLvl, children: "\u4E0B\u4E00\u6CE2" })
|
|
6543
|
+
] }) : null
|
|
6544
|
+
] }),
|
|
6545
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "dgame-controls dgame-tank-controls", children: [
|
|
6546
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", "aria-label": "\u5411\u4E0A", onClick: () => {
|
|
6547
|
+
keys.current.dir = "up";
|
|
6548
|
+
setTimeout(() => {
|
|
6549
|
+
if (keys.current.dir === "up") keys.current.dir = null;
|
|
6550
|
+
}, 90);
|
|
6551
|
+
}, children: "\u2191" }),
|
|
6552
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "\u65B9\u5411\u952E / WASD \xB7 \u7A7A\u683C\u5F00\u70AE \xB7 R \u91CD\u5F00" }),
|
|
6553
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "dgame-tank-fire", "aria-label": "\u5F00\u70AE", onClick: () => {
|
|
6554
|
+
keys.current.shoot = true;
|
|
6555
|
+
setTimeout(() => {
|
|
6556
|
+
keys.current.shoot = false;
|
|
6557
|
+
}, 90);
|
|
6558
|
+
}, children: "\u5F00\u70AE" })
|
|
6559
|
+
] })
|
|
6560
|
+
] });
|
|
6561
|
+
}
|
|
6562
|
+
function TankPreview() {
|
|
6563
|
+
const ROWS3 = [
|
|
6564
|
+
"..B..B..",
|
|
6565
|
+
"S.....E.",
|
|
6566
|
+
"..B.B...",
|
|
6567
|
+
"B..S...B",
|
|
6568
|
+
"...P..F."
|
|
6569
|
+
];
|
|
6570
|
+
const cls = { ".": "void", B: "brick", S: "steel", P: "pw", E: "en", F: "base" };
|
|
6571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "dgcov-prev dgcov-prev-tank", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "dgp-tank-grid", children: ROWS3.flatMap((row, r) => row.split("").map((ch, c) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("i", { className: "dgp-tank-" + cls[ch] }, r + "-" + c))) }) });
|
|
6572
|
+
}
|
|
6573
|
+
var tankGame = { Game: TankGame, Preview: TankPreview, css: `
|
|
6574
|
+
.dgame-tank-stage{position:relative;align-self:center;width:min(100%,320px);aspect-ratio:1/1;border:1px solid color-mix(in srgb,rgb(250 204 21) 32%,var(--dsw-alias-border-l1));border-radius:8px;overflow:hidden;outline:none;background:linear-gradient(180deg,#1a1425,#100d1c);box-shadow:inset 0 0 26px rgb(0 0 0/.5)}.dgame-tank-stage:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,rgb(250 204 21) 30%,transparent)}.dgame-tank-grid{display:grid;grid-template-columns:repeat(15,1fr);grid-template-rows:repeat(15,1fr);width:100%;height:100%;gap:0}.dgame-tank-cell{position:relative;background:rgba(255,255,255,.03);min-width:0;min-height:0}.dgame-tank-cell.steel{background:linear-gradient(180deg,#94a3b8,#64748b)}.dgame-tank-cell.brick{background:repeating-linear-gradient(45deg,#a3672a 0 55%,#8a5a1c 55% 100%);box-shadow:inset 0 0 0 1px rgba(0,0,0,.25)}.dgame-tank-cell.base{background:#fbbf24}.dgame-tank-cell.bullet::before{content:'';position:absolute;inset:38%;background:#fef3c7;box-shadow:0 0 5px #fef3c7;z-index:4}.dgame-tank-flag{position:absolute;inset:0;display:grid;place-items:center;color:#7c2d12;font-size:18px;z-index:2}.dgame-tank-body{position:absolute;inset:16%;display:block;border-radius:16%;z-index:3;transition:transform .1s ease}.dgame-tank-body.pw{background:linear-gradient(135deg,#fde047,#eab308);box-shadow:0 0 7px color-mix(in srgb,#eab308 55%,transparent)}.dgame-tank-body.en{background:#f87171;box-shadow:0 0 5px color-mix(in srgb,#f87171 40%,transparent)}.dgame-tank-body::before{content:'';position:absolute;left:38%;top:-30%;width:24%;height:64%;background:rgba(255,255,255,.5);border-radius:3px}.dgame-tank-controls{grid-template-columns:1fr auto 1fr}
|
|
6575
|
+
` };
|
|
6576
|
+
|
|
6577
|
+
// features/games/view.jsx
|
|
6578
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
6579
|
+
var LANES = 3;
|
|
6580
|
+
var REACTOR_NEIGHBORS = [[0, 1, 3], [1, 0, 2, 4], [2, 1, 5], [3, 0, 4, 6], [4, 1, 3, 5, 7], [5, 2, 4, 8], [6, 3, 7], [7, 4, 6, 8], [8, 5, 7]];
|
|
6581
|
+
var FAB_POS_KEY = "dsh-dock/games/fab/v1";
|
|
6582
|
+
var WIN_GEOM_KEY = "dsh-dock/games/win/v1";
|
|
6583
|
+
var FAB_W = 36;
|
|
6584
|
+
var FAB_H = 120;
|
|
6585
|
+
var WIN_MIN_W = 360;
|
|
6586
|
+
var WIN_MIN_H = 340;
|
|
6587
|
+
function phaseLabel2(phase) {
|
|
6588
|
+
return { think: "\u601D\u8003\u4E2D", write: "\u8F93\u51FA\u4E2D", code: "\u7F16\u5199\u4EE3\u7801", search: "\u67E5\u8D44\u6599" }[phase] || "\u5904\u7406\u4E2D";
|
|
6589
|
+
}
|
|
6590
|
+
function elapsedText(ms) {
|
|
6591
|
+
const sec = Math.max(0, Math.floor((Number(ms) || 0) / 1e3));
|
|
6592
|
+
const min = Math.floor(sec / 60);
|
|
6593
|
+
return min > 0 ? min + " \u5206 " + String(sec % 60).padStart(2, "0") + " \u79D2" : sec + " \u79D2";
|
|
6594
|
+
}
|
|
6595
|
+
function makeReactor() {
|
|
6596
|
+
let cells = Array(9).fill(true);
|
|
6597
|
+
const seed = [4, 0, 8, 1, 7, 3];
|
|
6598
|
+
for (const index of seed) {
|
|
6599
|
+
for (const target of REACTOR_NEIGHBORS[index]) cells[target] = !cells[target];
|
|
6600
|
+
}
|
|
6601
|
+
return cells;
|
|
6602
|
+
}
|
|
6603
|
+
function initialDash() {
|
|
6604
|
+
return { lane: 1, rocks: [], score: 0, shield: 3, running: true, nextId: 1 };
|
|
6605
|
+
}
|
|
6606
|
+
function useTaskPulse() {
|
|
6607
|
+
const [status, setStatus] = (0, import_react18.useState)({ loading: true, active: [], error: false });
|
|
6608
|
+
const refresh = (0, import_react18.useCallback)(() => fetch("/dsh-dock/animation/status", {
|
|
6609
|
+
method: "POST",
|
|
6610
|
+
headers: { "content-type": "application/json" },
|
|
6611
|
+
body: "{}"
|
|
6612
|
+
}).then((res) => res.json()).then((payload) => {
|
|
6613
|
+
if (!payload || !payload.ok) throw new Error("status unavailable");
|
|
6614
|
+
setStatus({ loading: false, active: Array.isArray(payload.data && payload.data.active) ? payload.data.active : [], error: false });
|
|
6615
|
+
}).catch(() => setStatus((current) => ({ loading: false, active: current.active || [], error: true }))), []);
|
|
6616
|
+
(0, import_react18.useEffect)(() => {
|
|
6617
|
+
refresh();
|
|
6618
|
+
const timer = setInterval(refresh, 2600);
|
|
6619
|
+
return () => clearInterval(timer);
|
|
6620
|
+
}, [refresh]);
|
|
6621
|
+
return status;
|
|
6622
|
+
}
|
|
6623
|
+
function DashGame(props) {
|
|
6624
|
+
const [dash, setDash] = (0, import_react18.useState)(initialDash);
|
|
6625
|
+
const pausedRef = (0, import_react18.useRef)(!!(props && props.paused));
|
|
6626
|
+
pausedRef.current = !!(props && props.paused);
|
|
6627
|
+
const move = (0, import_react18.useCallback)((delta) => setDash((current) => Object.assign({}, current, {
|
|
6628
|
+
lane: Math.max(0, Math.min(LANES - 1, current.lane + delta))
|
|
6629
|
+
})), []);
|
|
6630
|
+
const stageRef = (0, import_react18.useRef)(null);
|
|
6631
|
+
const onKeyDown = (0, import_react18.useCallback)((event) => {
|
|
6632
|
+
if (event.key === "ArrowLeft" || event.key.toLowerCase() === "a") {
|
|
6633
|
+
event.preventDefault();
|
|
6634
|
+
move(-1);
|
|
6635
|
+
} else if (event.key === "ArrowRight" || event.key.toLowerCase() === "d") {
|
|
6636
|
+
event.preventDefault();
|
|
6637
|
+
move(1);
|
|
6638
|
+
} else if (!dash.running && (event.key === "Enter" || event.key === " ")) {
|
|
6639
|
+
event.preventDefault();
|
|
6640
|
+
setDash(initialDash());
|
|
6641
|
+
}
|
|
6642
|
+
}, [move, dash.running]);
|
|
6643
|
+
useGameControls(stageRef, props.paused, onKeyDown);
|
|
6644
|
+
(0, import_react18.useEffect)(() => {
|
|
6645
|
+
const timer = setInterval(() => {
|
|
6646
|
+
if (pausedRef.current || typeof document !== "undefined" && document.hidden) return;
|
|
6647
|
+
setDash((current) => {
|
|
6648
|
+
if (!current.running) return current;
|
|
6649
|
+
let gained = 0;
|
|
6650
|
+
let hit = false;
|
|
6651
|
+
const rocks = [];
|
|
6652
|
+
for (const rock of current.rocks) {
|
|
6653
|
+
const next = Object.assign({}, rock, { row: rock.row + 1 });
|
|
6654
|
+
if (next.row >= 4) {
|
|
6655
|
+
if (next.lane === current.lane) hit = true;
|
|
6656
|
+
else gained += 1;
|
|
6657
|
+
} else rocks.push(next);
|
|
6658
|
+
}
|
|
6659
|
+
const shield = hit ? current.shield - 1 : current.shield;
|
|
6660
|
+
if (Math.random() > 0.32) rocks.push({ id: current.nextId, lane: Math.floor(Math.random() * LANES), row: 0 });
|
|
6661
|
+
return {
|
|
6662
|
+
lane: current.lane,
|
|
6663
|
+
rocks,
|
|
6664
|
+
score: current.score + gained,
|
|
6665
|
+
shield,
|
|
6666
|
+
running: shield > 0,
|
|
6667
|
+
nextId: current.nextId + 1
|
|
6668
|
+
};
|
|
6669
|
+
});
|
|
6670
|
+
}, 620);
|
|
6671
|
+
return () => clearInterval(timer);
|
|
6672
|
+
}, []);
|
|
6673
|
+
const cells = [];
|
|
6674
|
+
for (let row = 0; row < 5; row += 1) {
|
|
6675
|
+
for (let lane = 0; lane < LANES; lane += 1) {
|
|
6676
|
+
const rock = dash.rocks.find((item) => item.row === row && item.lane === lane);
|
|
6677
|
+
const pilot = row === 4 && lane === dash.lane;
|
|
6678
|
+
cells.push(/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "dgame-dash-cell", children: [
|
|
6679
|
+
rock ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", { className: "dgame-rock", children: "\u25C6" }) : null,
|
|
6680
|
+
pilot ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("b", { className: "dgame-pilot", children: "\u25B2" }) : null
|
|
6681
|
+
] }, row + "-" + lane));
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: "dgame-game", "aria-label": "\u661F\u9645\u8EB2\u907F\u5C0F\u6E38\u620F", children: [
|
|
6685
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgame-game-head", children: [
|
|
6686
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
|
|
6687
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { children: "\u661F\u9645\u8EB2\u907F" }),
|
|
6688
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { children: "\u5DE6\u53F3\u79FB\u52A8\u8865\u7ED9\u8247\uFF0C\u907F\u5F00\u6B63\u5728\u5760\u843D\u7684\u9668\u77F3\u3002" })
|
|
6689
|
+
] }),
|
|
6690
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgame-score", children: [
|
|
6691
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
|
|
6692
|
+
"\u5F97\u5206 ",
|
|
6693
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: dash.score })
|
|
6694
|
+
] }),
|
|
6695
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { "aria-label": "\u62A4\u76FE " + dash.shield + " \u683C", children: [
|
|
6696
|
+
"\u62A4\u76FE ",
|
|
6697
|
+
"\u2726".repeat(Math.max(0, dash.shield)),
|
|
6698
|
+
"\xB7".repeat(Math.max(0, 3 - dash.shield))
|
|
6699
|
+
] })
|
|
6700
|
+
] })
|
|
6701
|
+
] }),
|
|
6702
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgame-dash-stage", ref: stageRef, tabIndex: 0, onKeyDown, onClick: () => focusStage(stageRef), "aria-label": "\u661F\u9645\u8EB2\u907F\u6E38\u620F\u533A\u57DF\uFF0C\u4F7F\u7528\u5DE6\u53F3\u65B9\u5411\u952E\u6216\u4E0B\u65B9\u6309\u94AE\u79FB\u52A8", children: [
|
|
6703
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "dgame-dash-grid", children: cells }),
|
|
6704
|
+
!dash.running ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgame-over", children: [
|
|
6705
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: "\u62A4\u76FE\u8017\u5C3D" }),
|
|
6706
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", onClick: () => setDash(initialDash()), children: "\u91CD\u65B0\u51FA\u53D1" })
|
|
6707
|
+
] }) : null
|
|
6708
|
+
] }),
|
|
6709
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgame-controls", children: [
|
|
6710
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", "aria-label": "\u5411\u5DE6\u79FB\u52A8", onClick: () => move(-1), children: "\u2190 \u5DE6\u79FB" }),
|
|
6711
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "\u65B9\u5411\u952E / A\u3001D \xB7 \u7A7A\u683C\u91CD\u5F00" }),
|
|
6712
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { type: "button", "aria-label": "\u5411\u53F3\u79FB\u52A8", onClick: () => move(1), children: [
|
|
6713
|
+
"\u53F3\u79FB ",
|
|
6714
|
+
"->"
|
|
6715
|
+
] })
|
|
6716
|
+
] }),
|
|
6717
|
+
" "
|
|
6718
|
+
] });
|
|
6719
|
+
}
|
|
6720
|
+
function ReactorGame(props) {
|
|
6721
|
+
const [cells, setCells] = (0, import_react18.useState)(makeReactor);
|
|
6722
|
+
const [moves, setMoves] = (0, import_react18.useState)(0);
|
|
6723
|
+
const [cursor, setCursor] = (0, import_react18.useState)(4);
|
|
6724
|
+
const gridRef = (0, import_react18.useRef)(null);
|
|
6725
|
+
const solved = cells.every(Boolean);
|
|
6726
|
+
const toggle = (index) => {
|
|
6727
|
+
if (solved) return;
|
|
6728
|
+
setCells((current) => current.map((on, target) => REACTOR_NEIGHBORS[index].includes(target) ? !on : on));
|
|
6729
|
+
setMoves((current) => current + 1);
|
|
6730
|
+
};
|
|
6731
|
+
const reset = () => {
|
|
6732
|
+
setCells(makeReactor());
|
|
6733
|
+
setMoves(0);
|
|
6734
|
+
};
|
|
6735
|
+
const moveCursor = (dRow, dCol) => setCursor((c) => {
|
|
6736
|
+
const row = Math.min(2, Math.max(0, Math.floor(c / 3) + dRow));
|
|
6737
|
+
const col = Math.min(2, Math.max(0, c % 3 + dCol));
|
|
6738
|
+
return row * 3 + col;
|
|
6739
|
+
});
|
|
6740
|
+
const onKeyDown = (event) => {
|
|
6741
|
+
const key = event.key;
|
|
6742
|
+
if (key === "ArrowLeft") {
|
|
6743
|
+
event.preventDefault();
|
|
6744
|
+
moveCursor(0, -1);
|
|
6745
|
+
} else if (key === "ArrowRight") {
|
|
6746
|
+
event.preventDefault();
|
|
6747
|
+
moveCursor(0, 1);
|
|
6748
|
+
} else if (key === "ArrowUp") {
|
|
6749
|
+
event.preventDefault();
|
|
6750
|
+
moveCursor(-1, 0);
|
|
6751
|
+
} else if (key === "ArrowDown") {
|
|
6752
|
+
event.preventDefault();
|
|
6753
|
+
moveCursor(1, 0);
|
|
6754
|
+
} else if (key === "Enter" || key === " ") {
|
|
6755
|
+
event.preventDefault();
|
|
6756
|
+
toggle(cursor);
|
|
6757
|
+
} else if (key.toLowerCase() === "r") {
|
|
6758
|
+
event.preventDefault();
|
|
6759
|
+
reset();
|
|
6760
|
+
}
|
|
6761
|
+
};
|
|
6762
|
+
useGameControls(gridRef, props.paused, onKeyDown);
|
|
6763
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: "dgame-game", "aria-label": "\u53CD\u5E94\u5806\u70B9\u4EAE\u5C0F\u6E38\u620F", children: [
|
|
6764
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgame-game-head", children: [
|
|
6765
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
|
|
6766
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { children: "\u53CD\u5E94\u5806\u70B9\u4EAE" }),
|
|
6767
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { children: "\u70B9\u4E00\u4E0B\u4F1A\u5207\u6362\u76F8\u90BB\u6A21\u5757\uFF0C\u628A\u4E5D\u4E2A\u6A21\u5757\u5168\u90E8\u70B9\u4EAE\uFF1B\u952E\u76D8\uFF1A\u65B9\u5411\u952E\u9009\u683C\u3001Enter/\u7A7A\u683C \u70B9\u4EAE\u3001R \u91CD\u7F6E\u3002" })
|
|
6768
|
+
] }),
|
|
6769
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgame-score", children: [
|
|
6770
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
|
|
6771
|
+
"\u64CD\u4F5C ",
|
|
6772
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: moves })
|
|
6773
|
+
] }),
|
|
6774
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", onClick: reset, children: "\u91CD\u7F6E" })
|
|
6775
|
+
] })
|
|
6776
|
+
] }),
|
|
6777
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "dgame-reactor", ref: gridRef, tabIndex: 0, role: "group", "aria-label": "\u4E5D\u5BAB\u683C\u53CD\u5E94\u5806", onKeyDown, onClick: () => focusStage(gridRef), children: cells.map((on, index) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", tabIndex: -1, className: "dgame-reactor-cell" + (on ? " on" : "") + (cursor === index ? " focus" : ""), "aria-current": cursor === index ? "true" : void 0, "aria-label": (on ? "\u5DF2\u70B9\u4EAE" : "\u672A\u70B9\u4EAE") + "\u6A21\u5757 " + (index + 1), onClick: () => {
|
|
6778
|
+
toggle(index);
|
|
6779
|
+
setCursor(index);
|
|
6780
|
+
focusStage(gridRef);
|
|
6781
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}) }, index)) }),
|
|
6782
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "dgame-reactor-status" + (solved ? " solved" : ""), children: solved ? "\u53CD\u5E94\u5806\u7A33\u5B9A\u8FD0\u884C \xB7 \u505A\u5F97\u6F02\u4EAE\uFF08R \u91CD\u65B0\u5F00\u59CB\uFF09" : "\u8BA9\u6240\u6709\u6A21\u5757\u53D1\u5149\uFF0C\u7ED9\u4EFB\u52A1\u4E00\u70B9\u80FD\u91CF" })
|
|
6783
|
+
] });
|
|
6784
|
+
}
|
|
6785
|
+
function DashCover() {
|
|
6786
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "dgcov-prev dgcov-prev-dash", "aria-hidden": "true", children: [
|
|
6787
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}),
|
|
6788
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}),
|
|
6789
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}),
|
|
6790
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("b", {})
|
|
6791
|
+
] });
|
|
6792
|
+
}
|
|
6793
|
+
function ReactorCover() {
|
|
6794
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dgcov-prev dgcov-prev-reactor", "aria-hidden": "true", children: Array.from({ length: 9 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}, i)) });
|
|
6795
|
+
}
|
|
6796
|
+
var GAMES = [
|
|
6797
|
+
{
|
|
6798
|
+
id: "dash",
|
|
6799
|
+
name: "\u661F\u9645\u8EB2\u907F",
|
|
6800
|
+
cat: "casual",
|
|
6801
|
+
accent: "#38bdf8",
|
|
6802
|
+
desc: "\u5DE6\u53F3\u79FB\u52A8\u8865\u7ED9\u8247\uFF0C\u907F\u5F00\u5760\u843D\u7684\u9668\u77F3\uFF1B\u62A4\u76FE\u8017\u5C3D\u524D\u80FD\u62FF\u591A\u5C11\u5206\uFF1F",
|
|
6803
|
+
tip: "\u2190/\u2192 \u6216 A\u3001D \u79FB\u52A8 \xB7 \u7A7A\u683C\u91CD\u5F00",
|
|
6804
|
+
Game: DashGame,
|
|
6805
|
+
Preview: DashCover
|
|
6806
|
+
},
|
|
6807
|
+
{
|
|
6808
|
+
id: "reactor",
|
|
6809
|
+
name: "\u53CD\u5E94\u5806\u70B9\u4EAE",
|
|
6810
|
+
cat: "casual",
|
|
6811
|
+
accent: "#a78bfa",
|
|
6812
|
+
desc: "\u70B9\u4E00\u4E0B\u4F1A\u7FFB\u8F6C\u76F8\u90BB\u6A21\u5757\uFF0C\u628A\u4E5D\u4E2A\u6A21\u5757\u5168\u90E8\u70B9\u4EAE\uFF0C\u8BA9\u53CD\u5E94\u5806\u7A33\u5B9A\u8FD0\u884C\uFF1B\u952E\u76D8\u5168\u7A0B\u53EF\u73A9\u3002",
|
|
6813
|
+
tip: "\u65B9\u5411\u952E\u9009\u683C \xB7 Enter/\u7A7A\u683C \u70B9\u4EAE \xB7 R \u91CD\u7F6E",
|
|
6814
|
+
Game: ReactorGame,
|
|
6815
|
+
Preview: ReactorCover
|
|
6816
|
+
},
|
|
6817
|
+
{
|
|
6818
|
+
id: "gomoku",
|
|
6819
|
+
name: "\u4E94\u5B50\u68CB",
|
|
6820
|
+
cat: "board",
|
|
6821
|
+
accent: "#38bdf8",
|
|
6822
|
+
desc: "\u4F60\u6267\u9ED1\u5148\u624B\u3001AI \u6267\u767D\uFF1B\u5148\u5728\u6A2A\u7AD6\u659C\u4EFB\u4E00\u65B9\u5411\u8FDE\u6210\u4E94\u5B50\u8005\u80DC\u3002",
|
|
6823
|
+
tip: "\u70B9\u51FB\u7A7A\u4F4D\u843D\u5B50 \xB7 \u64A4\u9500 / \u91CD\u5F00",
|
|
6824
|
+
Game: gomokuGame.Game,
|
|
6825
|
+
Preview: gomokuGame.Preview
|
|
6826
|
+
},
|
|
6827
|
+
{
|
|
6828
|
+
id: "xiangqi",
|
|
6829
|
+
name: "\u4E2D\u56FD\u8C61\u68CB",
|
|
6830
|
+
cat: "board",
|
|
6831
|
+
accent: "#f87171",
|
|
6832
|
+
desc: "\u4F60\u6267\u7EA2\u5148\u884C\u3001AI \u6267\u9ED1\uFF1B\u5403\u5149\u5BF9\u65B9\u5C06/\u5E05\u5373\u80DC\uFF0C\u522B\u9001\u738B\u88AB\u5C06\u3002",
|
|
6833
|
+
tip: "\u9009\u4E2D\u7EA2\u5B50\u518D\u70B9\u843D\u70B9 \xB7 R \u91CD\u5F00 \xB7 U \u64A4\u9500",
|
|
6834
|
+
Game: xiangqiGame.Game,
|
|
6835
|
+
Preview: xiangqiGame.Preview
|
|
6836
|
+
},
|
|
6837
|
+
{
|
|
6838
|
+
id: "tetris",
|
|
6839
|
+
name: "\u4FC4\u7F57\u65AF\u65B9\u5757",
|
|
6840
|
+
cat: "classic",
|
|
6841
|
+
accent: "#22d3ee",
|
|
6842
|
+
desc: "\u7ECF\u5178\u673A\u53F0\u7B2C\u4E00\u6B3E\uFF1A\u62FC\u6EE1\u4E00\u884C\u6D88\u9664\u5F97\u5206\uFF0C\u901F\u5EA6\u968F\u7B49\u7EA7\u52A0\u5FEB\u3002",
|
|
6843
|
+
tip: "\u2190/\u2192 \u79FB\u52A8 \xB7 \u2193 \u4E0B\u843D \xB7 \u2191/X \u65CB\u8F6C \xB7 \u7A7A\u683C \u76F4\u843D \xB7 P \u6682\u505C",
|
|
6844
|
+
Game: tetrisGame.Game,
|
|
6845
|
+
Preview: tetrisGame.Preview
|
|
6846
|
+
},
|
|
6847
|
+
{
|
|
6848
|
+
id: "sokoban",
|
|
6849
|
+
name: "\u63A8\u7BB1\u5B50",
|
|
6850
|
+
cat: "classic",
|
|
6851
|
+
accent: "#facc15",
|
|
6852
|
+
desc: "\u628A\u7BB1\u5B50\u5168\u90E8\u63A8\u5230\u76EE\u6807\u70B9\u4E0A\u901A\u5173\uFF1B\u516D\u5173\u96BE\u5EA6\u9012\u589E\u3002",
|
|
6853
|
+
tip: "\u65B9\u5411\u952E\u79FB\u52A8 \xB7 U \u64A4\u9500 \xB7 R \u91CD\u7F6E \xB7 \u6570\u5B57\u5207\u5173",
|
|
6854
|
+
Game: sokobanGame.Game,
|
|
6855
|
+
Preview: sokobanGame.Preview
|
|
6856
|
+
},
|
|
6857
|
+
{
|
|
6858
|
+
id: "snake",
|
|
6859
|
+
name: "\u8D2A\u5403\u86C7",
|
|
6860
|
+
cat: "classic",
|
|
6861
|
+
accent: "#4ade80",
|
|
6862
|
+
desc: "\u5403\u5230\u98DF\u7269\u53D8\u957F\u5E76\u52A0\u901F\uFF1B\u649E\u5899\u6216\u54AC\u5230\u81EA\u5DF1\u5C31\u7ED3\u675F\u3002",
|
|
6863
|
+
tip: "\u65B9\u5411\u952E / WASD \u79FB\u52A8 \xB7 P \u6682\u505C \xB7 R \u91CD\u5F00",
|
|
6864
|
+
Game: snakeGame.Game,
|
|
6865
|
+
Preview: snakeGame.Preview
|
|
6866
|
+
},
|
|
6867
|
+
{
|
|
6868
|
+
id: "breakout",
|
|
6869
|
+
name: "\u6253\u7816\u5757",
|
|
6870
|
+
cat: "classic",
|
|
6871
|
+
accent: "#38bdf8",
|
|
6872
|
+
desc: "\u79FB\u52A8\u6321\u677F\u53CD\u5F39\u5C0F\u7403\uFF0C\u51FB\u788E\u5168\u90E8\u7816\u5757\uFF1B\u7403\u6389\u5230\u5E95\u90E8\u4E22\u4E00\u6761\u547D\u3002",
|
|
6873
|
+
tip: "\u2190/\u2192 \u6216\u9F20\u6807\u79FB\u52A8 \xB7 \u7A7A\u683C\u53D1\u7403",
|
|
6874
|
+
Game: breakoutGame.Game,
|
|
6875
|
+
Preview: breakoutGame.Preview
|
|
6876
|
+
},
|
|
6877
|
+
{
|
|
6878
|
+
id: "racer",
|
|
6879
|
+
name: "\u6781\u901F\u8D5B\u8F66",
|
|
6880
|
+
cat: "classic",
|
|
6881
|
+
accent: "#fb923c",
|
|
6882
|
+
desc: "\u907F\u5F00\u8FCE\u9762\u6765\u8F66\u3001\u6536\u96C6\u91D1\u5E01\uFF0C\u8D8A\u5F00\u8D8A\u5FEB\uFF1B\u649E\u8F66\u5373\u7ED3\u675F\u3002",
|
|
6883
|
+
tip: "\u2190/\u2192 \u6216\u9F20\u6807\u79FB\u52A8 \xB7 R \u91CD\u5F00",
|
|
6884
|
+
Game: racerGame.Game,
|
|
6885
|
+
Preview: racerGame.Preview
|
|
6886
|
+
},
|
|
6887
|
+
{
|
|
6888
|
+
id: "tank",
|
|
6889
|
+
name: "\u5766\u514B\u5927\u6218",
|
|
6890
|
+
cat: "classic",
|
|
6891
|
+
accent: "#fde047",
|
|
6892
|
+
desc: "\u51FB\u6BC1\u654C\u65B9\u5766\u514B\u3001\u5B88\u4F4F\u4E2D\u592E\u57FA\u5730\uFF1B\u6E05\u7A7A\u4E00\u6CE2\u8FDB\u5165\u4E0B\u4E00\u6CE2\u3002",
|
|
6893
|
+
tip: "\u65B9\u5411\u952E\u79FB\u52A8 \xB7 \u7A7A\u683C\u5F00\u70AE \xB7 R \u91CD\u5F00",
|
|
6894
|
+
Game: tankGame.Game,
|
|
6895
|
+
Preview: tankGame.Preview
|
|
6896
|
+
}
|
|
6897
|
+
];
|
|
6898
|
+
var gamesBus = {
|
|
6899
|
+
state: { open: false, game: null, picker: false },
|
|
6900
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
6901
|
+
subscribe(fn) {
|
|
6902
|
+
this.listeners.add(fn);
|
|
6903
|
+
return () => {
|
|
6904
|
+
this.listeners.delete(fn);
|
|
6905
|
+
};
|
|
6906
|
+
},
|
|
6907
|
+
emit() {
|
|
6908
|
+
for (const fn of this.listeners) fn();
|
|
6909
|
+
},
|
|
6910
|
+
launch(id) {
|
|
6911
|
+
if (!GAMES.some((g) => g.id === id)) return;
|
|
6912
|
+
this.state = { open: true, game: id, picker: false };
|
|
6913
|
+
this.emit();
|
|
6914
|
+
},
|
|
6915
|
+
// 快捷入口点击:上次玩过的直接续玩,否则先展开选择层。
|
|
6916
|
+
entry() {
|
|
6917
|
+
this.state = { open: true, game: this.state.game, picker: this.state.game ? false : true };
|
|
6918
|
+
this.emit();
|
|
6919
|
+
},
|
|
6920
|
+
togglePicker() {
|
|
6921
|
+
this.state = Object.assign({}, this.state, { picker: !this.state.picker });
|
|
6922
|
+
this.emit();
|
|
6923
|
+
},
|
|
6924
|
+
closePicker() {
|
|
6925
|
+
this.state = Object.assign({}, this.state, { picker: false });
|
|
6926
|
+
this.emit();
|
|
6927
|
+
},
|
|
6928
|
+
close() {
|
|
6929
|
+
this.state = { open: false, game: this.state.game, picker: false };
|
|
6930
|
+
this.emit();
|
|
6931
|
+
}
|
|
6932
|
+
};
|
|
6933
|
+
function useGameWin() {
|
|
6934
|
+
const [state, setState] = (0, import_react18.useState)(gamesBus.state);
|
|
6935
|
+
(0, import_react18.useEffect)(() => gamesBus.subscribe(() => setState(gamesBus.state)), []);
|
|
6936
|
+
return state;
|
|
6937
|
+
}
|
|
6938
|
+
var CATS = [
|
|
6939
|
+
{ id: "board", label: "\u68CB\u76D8\u5BF9\u6218", desc: "\u4F60\u4E0E AI \u5BF9\u5F08" },
|
|
6940
|
+
{ id: "classic", label: "\u7ECF\u5178\u673A\u53F0", desc: "\u4FC4\u7F57\u65AF\u65B9\u5757\u6E38\u620F\u673A\u91CC\u7684\u6000\u65E7\u6E38\u620F" },
|
|
6941
|
+
{ id: "casual", label: "\u4F11\u95F2\u76CA\u667A", desc: "\u7B49\u4EFB\u52A1\u65F6\u987A\u624B\u6765\u4E00\u628A" }
|
|
6942
|
+
];
|
|
6943
|
+
function GameCoverGrid(props) {
|
|
6944
|
+
const currentId = props && props.currentId;
|
|
6945
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "dgcov-groups", children: CATS.map((cat) => {
|
|
6946
|
+
const list = GAMES.filter((g) => (g.cat || "casual") === cat.id);
|
|
6947
|
+
if (!list.length) return null;
|
|
6948
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgcov-section", children: [
|
|
6949
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgcov-sechead", children: [
|
|
6950
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("b", { children: cat.label }),
|
|
6951
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: cat.desc }),
|
|
6952
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("em", { children: [
|
|
6953
|
+
list.length,
|
|
6954
|
+
" \u6B3E"
|
|
6955
|
+
] })
|
|
6956
|
+
] }),
|
|
6957
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "dgcov-grid", role: "list", children: list.map((g) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
6958
|
+
"button",
|
|
6959
|
+
{
|
|
6960
|
+
type: "button",
|
|
6961
|
+
role: "listitem",
|
|
6962
|
+
className: "dgcov" + (currentId === g.id ? " on" : ""),
|
|
6963
|
+
onClick: () => props.onLaunch(g.id),
|
|
6964
|
+
children: [
|
|
6965
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(g.Preview, {}),
|
|
6966
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "dgcov-name", children: [
|
|
6967
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", { style: { background: g.accent } }),
|
|
6968
|
+
g.name,
|
|
6969
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("em", { children: currentId === g.id ? "\u6B63\u5728\u73A9" : "\u5F00\u73A9" })
|
|
6970
|
+
] }),
|
|
6971
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dgcov-desc", children: g.desc })
|
|
6972
|
+
]
|
|
6973
|
+
},
|
|
6974
|
+
g.id
|
|
6975
|
+
)) })
|
|
6976
|
+
] }, cat.id);
|
|
6977
|
+
}) });
|
|
6978
|
+
}
|
|
6979
|
+
function TaskPill({ status }) {
|
|
6980
|
+
if (status.loading) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dgwin-task", children: "\u6B63\u5728\u8BFB\u53D6\u4EFB\u52A1\u72B6\u6001\u2026" });
|
|
6981
|
+
const task = status.active[0];
|
|
6982
|
+
if (!task) return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "dgwin-task idle", children: [
|
|
6983
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}),
|
|
6984
|
+
"\u4EFB\u52A1\u7A7A\u95F2 \xB7 \u6162\u6162\u73A9"
|
|
6985
|
+
] });
|
|
6986
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "dgwin-task live", children: [
|
|
6987
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}),
|
|
6988
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("b", { children: [
|
|
6989
|
+
status.active.length,
|
|
6990
|
+
" \u4E2A\u4EFB\u52A1"
|
|
6991
|
+
] }),
|
|
6992
|
+
phaseLabel2(task.phase),
|
|
6993
|
+
" \xB7 ",
|
|
6994
|
+
elapsedText(task.elapsed)
|
|
6995
|
+
] });
|
|
6996
|
+
}
|
|
6997
|
+
function readFabSide() {
|
|
6998
|
+
try {
|
|
6999
|
+
const p = JSON.parse(localStorage.getItem(FAB_POS_KEY) || "null");
|
|
7000
|
+
return p && (p.side === "left" || p.side === "right") ? p.side : "left";
|
|
7001
|
+
} catch {
|
|
7002
|
+
return "left";
|
|
7003
|
+
}
|
|
7004
|
+
}
|
|
7005
|
+
function defaultWinGeom() {
|
|
7006
|
+
const w = Math.min(470, window.innerWidth - 16);
|
|
7007
|
+
const h = Math.min(600, window.innerHeight - 16);
|
|
7008
|
+
const x = readFabSide() === "right" ? Math.max(10, window.innerWidth - w - 14) : Math.min(Math.max(10, sidebarRightEdge() + 10), Math.max(10, window.innerWidth - w - 14));
|
|
7009
|
+
return { mode: "normal", x, y: Math.max(10, Math.round((window.innerHeight - h) / 2) - 10), w, h };
|
|
7010
|
+
}
|
|
7011
|
+
function restoreWinGeom() {
|
|
7012
|
+
try {
|
|
7013
|
+
const g = JSON.parse(localStorage.getItem(WIN_GEOM_KEY) || "null");
|
|
7014
|
+
if (g && [g.x, g.y, g.w, g.h].every((v) => typeof v === "number")) {
|
|
7015
|
+
const w = Math.max(WIN_MIN_W, Math.min(g.w, window.innerWidth - 16));
|
|
7016
|
+
const h = Math.max(WIN_MIN_H, Math.min(g.h, window.innerHeight - 16));
|
|
7017
|
+
return {
|
|
7018
|
+
mode: "normal",
|
|
7019
|
+
x: Math.min(Math.max(8, g.x), Math.max(8, window.innerWidth - w - 8)),
|
|
7020
|
+
y: Math.min(Math.max(8, g.y), Math.max(8, window.innerHeight - h - 8)),
|
|
7021
|
+
w,
|
|
7022
|
+
h
|
|
7023
|
+
};
|
|
7024
|
+
}
|
|
7025
|
+
} catch {
|
|
7026
|
+
}
|
|
7027
|
+
return defaultWinGeom();
|
|
7028
|
+
}
|
|
7029
|
+
function findSidebarCol() {
|
|
7030
|
+
if (typeof document === "undefined") return null;
|
|
7031
|
+
const overlay = document.querySelector("[data-shell-overlay]");
|
|
7032
|
+
const frame = overlay && overlay.parentElement;
|
|
7033
|
+
if (!frame) return null;
|
|
7034
|
+
return frame.children[0] || null;
|
|
7035
|
+
}
|
|
7036
|
+
function sidebarRightEdge() {
|
|
7037
|
+
const col = findSidebarCol();
|
|
7038
|
+
if (col) {
|
|
7039
|
+
const rect2 = col.getBoundingClientRect();
|
|
7040
|
+
if (rect2 && typeof rect2.right === "number") return rect2.right;
|
|
7041
|
+
}
|
|
7042
|
+
return 280;
|
|
7043
|
+
}
|
|
7044
|
+
function snapFabX(side) {
|
|
7045
|
+
if (typeof window === "undefined") return 0;
|
|
7046
|
+
return side === "right" ? Math.max(0, window.innerWidth - FAB_W) : sidebarRightEdge();
|
|
7047
|
+
}
|
|
7048
|
+
function GameWindow(props) {
|
|
7049
|
+
const game = props.game;
|
|
7050
|
+
const status = useTaskPulse();
|
|
7051
|
+
const [win, setWin] = (0, import_react18.useState)(() => typeof window === "undefined" ? { mode: "normal", x: 14, y: 14, w: 470, h: 600 } : restoreWinGeom());
|
|
7052
|
+
const winRef = (0, import_react18.useRef)(win);
|
|
7053
|
+
winRef.current = win;
|
|
7054
|
+
const pickerRef = (0, import_react18.useRef)(props.picker);
|
|
7055
|
+
pickerRef.current = props.picker;
|
|
7056
|
+
(0, import_react18.useEffect)(() => {
|
|
7057
|
+
const onKey = (e) => {
|
|
7058
|
+
if (e.key !== "Escape") return;
|
|
7059
|
+
if (pickerRef.current) gamesBus.closePicker();
|
|
7060
|
+
else gamesBus.close();
|
|
7061
|
+
};
|
|
7062
|
+
window.addEventListener("keydown", onKey);
|
|
7063
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
7064
|
+
}, []);
|
|
7065
|
+
function persistGeom(s) {
|
|
7066
|
+
try {
|
|
7067
|
+
localStorage.setItem(WIN_GEOM_KEY, JSON.stringify({ x: s.x, y: s.y, w: s.w, h: s.h }));
|
|
7068
|
+
} catch {
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
function beginDrag(e, kind) {
|
|
7072
|
+
if (e.button !== 0 || typeof window === "undefined") return;
|
|
7073
|
+
if (kind === "move" && (winRef.current.mode === "max" || e.target && e.target.closest && e.target.closest("button"))) return;
|
|
7074
|
+
const startX = e.clientX, startY = e.clientY;
|
|
7075
|
+
const origin = Object.assign({}, winRef.current);
|
|
7076
|
+
const onMove = (ev) => {
|
|
7077
|
+
const dx = ev.clientX - startX, dy = ev.clientY - startY;
|
|
7078
|
+
if (kind === "move") setWin((s) => Object.assign({}, s, {
|
|
7079
|
+
x: Math.min(Math.max(8, origin.x + dx), Math.max(8, window.innerWidth - origin.w - 8)),
|
|
7080
|
+
y: Math.min(Math.max(8, origin.y + dy), Math.max(8, window.innerHeight - origin.h - 8))
|
|
7081
|
+
}));
|
|
7082
|
+
else setWin((s) => Object.assign({}, s, {
|
|
7083
|
+
// 窗口高度随内容自适应,缩放只调宽度;游戏板按比例跟随缩放。
|
|
7084
|
+
w: Math.max(WIN_MIN_W, Math.min(origin.w + dx, window.innerWidth - 16))
|
|
7085
|
+
}));
|
|
7086
|
+
};
|
|
7087
|
+
const onUp = () => {
|
|
7088
|
+
window.removeEventListener("pointermove", onMove);
|
|
7089
|
+
window.removeEventListener("pointerup", onUp);
|
|
7090
|
+
setWin((s) => {
|
|
7091
|
+
persistGeom(s);
|
|
7092
|
+
return s;
|
|
7093
|
+
});
|
|
7094
|
+
};
|
|
7095
|
+
window.addEventListener("pointermove", onMove);
|
|
7096
|
+
window.addEventListener("pointerup", onUp);
|
|
7097
|
+
}
|
|
7098
|
+
const Game = game && game.Game;
|
|
7099
|
+
const style = win.mode === "max" ? { left: 10, top: 10, right: 10, bottom: 10, width: "auto", height: "auto" } : { left: win.x, top: win.y, width: win.w, height: props.picker ? "min(76vh, 640px)" : "auto" };
|
|
7100
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgwin dgwin-" + win.mode, role: "dialog", "aria-label": "\u6E38\u620F\u7A97\u53E3\uFF1A" + (game ? game.name : "\u8DA3\u5473\u6E38\u620F"), style, children: [
|
|
7101
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
7102
|
+
"header",
|
|
7103
|
+
{
|
|
7104
|
+
className: "dgwin-bar",
|
|
7105
|
+
onPointerDown: (e) => beginDrag(e, "move"),
|
|
7106
|
+
onDoubleClick: () => setWin((s) => Object.assign({}, s, { mode: s.mode === "max" ? "normal" : "max" })),
|
|
7107
|
+
children: [
|
|
7108
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dgwin-ico", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(GamepadIcon, { size: 15 }) }),
|
|
7109
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgwin-title", children: [
|
|
7110
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("b", { children: game ? game.name : "\u8DA3\u5473\u6E38\u620F" }),
|
|
7111
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: game ? game.desc : "\u9009\u4E00\u6B3E\u5C0F\u6E38\u620F\uFF0C\u8FB9\u7B49\u8FB9\u73A9" })
|
|
7112
|
+
] }),
|
|
7113
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TaskPill, { status }),
|
|
7114
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgwin-ctrls", children: [
|
|
7115
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "dgwin-btn", title: props.picker ? "\u6536\u8D77\u9009\u62E9\u5C42\uFF0C\u56DE\u5230\u6E38\u620F" : "\u5C55\u5F00\u9009\u62E9\u5C42\uFF0C\u6362\u4E00\u6B3E\u6E38\u620F", onClick: () => gamesBus.togglePicker(), children: props.picker ? "\u8FD4\u56DE" : "\u6362\u6E38\u620F" }),
|
|
7116
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "dgwin-sq", title: win.mode === "min" ? "\u8FD8\u539F" : "\u6700\u5C0F\u5316", "aria-label": win.mode === "min" ? "\u8FD8\u539F\u7A97\u53E3" : "\u6700\u5C0F\u5316\u7A97\u53E3", onClick: () => setWin((s) => Object.assign({}, s, { mode: s.mode === "min" ? "normal" : "min" })), children: "\u2581" }),
|
|
7117
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "dgwin-sq", title: win.mode === "max" ? "\u8FD8\u539F" : "\u6700\u5927\u5316", "aria-label": win.mode === "max" ? "\u8FD8\u539F\u7A97\u53E3" : "\u6700\u5927\u5316\u7A97\u53E3", onClick: () => setWin((s) => Object.assign({}, s, { mode: s.mode === "max" ? "normal" : "max" })), children: win.mode === "max" ? "\u2750" : "\u25A2" }),
|
|
7118
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "dgwin-sq", title: "\u5173\u95ED\uFF08Esc\uFF09", "aria-label": "\u5173\u95ED\u6E38\u620F\u7A97\u53E3", onClick: () => gamesBus.close(), children: "\u2715" })
|
|
7119
|
+
] })
|
|
7120
|
+
]
|
|
7121
|
+
}
|
|
7122
|
+
),
|
|
7123
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgwin-body", children: [
|
|
7124
|
+
Game ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Game, { paused: props.picker || win.mode === "min" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "dgwin-empty", children: "\u4ECE\u300C\u6362\u6E38\u620F\u300D\u91CC\u6311\u4E00\u6B3E\u5F00\u59CB\u73A9\u3002" }),
|
|
7125
|
+
game ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("footer", { className: "dgwin-tip", children: [
|
|
7126
|
+
game.tip,
|
|
7127
|
+
" \xB7 Esc \u5173\u95ED\u7A97\u53E3"
|
|
7128
|
+
] }) : null,
|
|
7129
|
+
props.picker ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgwin-picker", role: "dialog", "aria-label": "\u9009\u62E9\u5C0F\u6E38\u620F", children: [
|
|
7130
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgwin-picker-head", children: [
|
|
7131
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
|
|
7132
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("b", { children: "\u9009\u62E9\u5C0F\u6E38\u620F" }),
|
|
7133
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "\u70B9\u51FB\u5C01\u9762\u5F00\u73A9\uFF1B\u7A97\u53E3\u4F4D\u7F6E\u548C\u5927\u5C0F\u968F\u4F60\u8C03" })
|
|
7134
|
+
] }),
|
|
7135
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "dgwin-picker-close", "aria-label": "\u5173\u95ED\u9009\u62E9\u5C42", title: "\u5173\u95ED", onClick: () => gamesBus.closePicker(), children: "\u2715" })
|
|
7136
|
+
] }),
|
|
7137
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(GameCoverGrid, { currentId: game && game.id, onLaunch: (id) => gamesBus.launch(id) })
|
|
7138
|
+
] }) : null
|
|
7139
|
+
] }),
|
|
7140
|
+
win.mode === "normal" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "dgwin-resize", title: "\u62D6\u52A8\u8C03\u6574\u7A97\u53E3\u5927\u5C0F", "aria-hidden": "true", onPointerDown: (e) => beginDrag(e, "size") }) : null
|
|
7141
|
+
] });
|
|
7142
|
+
}
|
|
7143
|
+
function GameFab(props) {
|
|
7144
|
+
const [pos, setPos] = (0, import_react18.useState)(null);
|
|
7145
|
+
const [dragging, setDragging] = (0, import_react18.useState)(false);
|
|
7146
|
+
const dragRef = (0, import_react18.useRef)(null);
|
|
7147
|
+
const clickBlockRef = (0, import_react18.useRef)(false);
|
|
7148
|
+
const sideRef = (0, import_react18.useRef)("left");
|
|
7149
|
+
(0, import_react18.useEffect)(() => {
|
|
7150
|
+
if (typeof window === "undefined") return;
|
|
7151
|
+
let next = { side: "left", y: Math.round(window.innerHeight * 0.34) };
|
|
7152
|
+
try {
|
|
7153
|
+
const p = JSON.parse(localStorage.getItem(FAB_POS_KEY) || "null");
|
|
7154
|
+
if (p && (p.side === "left" || p.side === "right") && typeof p.y === "number") next = { side: p.side, y: p.y };
|
|
7155
|
+
} catch {
|
|
7156
|
+
}
|
|
7157
|
+
sideRef.current = next.side;
|
|
7158
|
+
setPos({
|
|
7159
|
+
side: next.side,
|
|
7160
|
+
x: snapFabX(next.side),
|
|
7161
|
+
y: Math.min(Math.max(8, next.y), Math.max(8, window.innerHeight - FAB_H - 8))
|
|
7162
|
+
});
|
|
7163
|
+
}, []);
|
|
7164
|
+
(0, import_react18.useEffect)(() => {
|
|
7165
|
+
if (typeof window === "undefined") return;
|
|
7166
|
+
const update = () => {
|
|
7167
|
+
if (dragRef.current) return;
|
|
7168
|
+
setPos((cur) => cur ? Object.assign({}, cur, { x: snapFabX(cur.side) }) : cur);
|
|
7169
|
+
};
|
|
7170
|
+
const resizeTarget = findSidebarCol();
|
|
7171
|
+
let ro = null;
|
|
7172
|
+
if (typeof ResizeObserver !== "undefined" && resizeTarget) {
|
|
7173
|
+
ro = new ResizeObserver(update);
|
|
7174
|
+
ro.observe(resizeTarget);
|
|
7175
|
+
}
|
|
7176
|
+
window.addEventListener("resize", update);
|
|
7177
|
+
return () => {
|
|
7178
|
+
window.removeEventListener("resize", update);
|
|
7179
|
+
if (ro) ro.disconnect();
|
|
7180
|
+
};
|
|
7181
|
+
}, []);
|
|
7182
|
+
const onPointerDown = (e) => {
|
|
7183
|
+
if (e.button !== 0) return;
|
|
7184
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
7185
|
+
dragRef.current = { sx: e.clientX, sy: e.clientY, ox: r.left, oy: r.top, w: r.width || FAB_W, h: r.height || FAB_H, moved: false };
|
|
7186
|
+
try {
|
|
7187
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
7188
|
+
} catch {
|
|
7189
|
+
}
|
|
7190
|
+
};
|
|
7191
|
+
const onPointerMove = (e) => {
|
|
7192
|
+
const d = dragRef.current;
|
|
7193
|
+
if (!d || typeof window === "undefined") return;
|
|
7194
|
+
const dx = e.clientX - d.sx, dy = e.clientY - d.sy;
|
|
7195
|
+
if (!d.moved && Math.hypot(dx, dy) < 4) return;
|
|
7196
|
+
d.moved = true;
|
|
7197
|
+
setDragging(true);
|
|
7198
|
+
const side = d.ox + dx + d.w / 2 < window.innerWidth / 2 ? "left" : "right";
|
|
7199
|
+
sideRef.current = side;
|
|
7200
|
+
setPos({
|
|
7201
|
+
side,
|
|
7202
|
+
x: Math.min(Math.max(0, d.ox + dx), Math.max(0, window.innerWidth - d.w)),
|
|
7203
|
+
y: Math.min(Math.max(8, d.oy + dy), Math.max(8, window.innerHeight - d.h - 8))
|
|
7204
|
+
});
|
|
7205
|
+
};
|
|
7206
|
+
const onPointerUp = (e) => {
|
|
7207
|
+
const d = dragRef.current;
|
|
7208
|
+
dragRef.current = null;
|
|
7209
|
+
setDragging(false);
|
|
7210
|
+
if (!d || !d.moved) return;
|
|
7211
|
+
clickBlockRef.current = true;
|
|
7212
|
+
setTimeout(() => {
|
|
7213
|
+
clickBlockRef.current = false;
|
|
7214
|
+
}, 0);
|
|
7215
|
+
const dx = e.clientX - d.sx, dy = e.clientY - d.sy;
|
|
7216
|
+
const side = d.ox + dx + d.w / 2 < window.innerWidth / 2 ? "left" : "right";
|
|
7217
|
+
sideRef.current = side;
|
|
7218
|
+
const x = snapFabX(side);
|
|
7219
|
+
const y = Math.min(Math.max(8, d.oy + dy), Math.max(8, window.innerHeight - d.h - 8));
|
|
7220
|
+
setPos({ side, x, y });
|
|
7221
|
+
try {
|
|
7222
|
+
localStorage.setItem(FAB_POS_KEY, JSON.stringify({ side, y }));
|
|
7223
|
+
} catch {
|
|
7224
|
+
}
|
|
7225
|
+
};
|
|
7226
|
+
if (!pos) return null;
|
|
7227
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
7228
|
+
"button",
|
|
7229
|
+
{
|
|
7230
|
+
type: "button",
|
|
7231
|
+
className: "dgfab dgfab-side-" + pos.side + (dragging ? " dgfab-drag" : "") + (props.hidden ? " dgfab-hide" : ""),
|
|
7232
|
+
style: { left: pos.x + "px", top: pos.y + "px" },
|
|
7233
|
+
title: "\u8DA3\u5473\u6E38\u620F \xB7 \u70B9\u51FB\u5F00\u73A9\uFF1B\u6309\u4F4F\u53EF\u62D6\u5230\u4FA7\u680F\u53F3\u7F18\u6216\u5C4F\u5E55\u53F3\u4FA7\u3001\u4E0A\u4E0B\u632A\u4F4D",
|
|
7234
|
+
"aria-label": "\u8DA3\u5473\u6E38\u620F\u5FEB\u6377\u5165\u53E3\uFF0C\u53EF\u62D6\u52A8\u6362\u8FB9\u4E0E\u4E0A\u4E0B\u632A\u4F4D",
|
|
7235
|
+
onPointerDown,
|
|
7236
|
+
onPointerMove,
|
|
7237
|
+
onPointerUp,
|
|
7238
|
+
onClick: () => {
|
|
7239
|
+
if (!clickBlockRef.current) gamesBus.entry();
|
|
7240
|
+
},
|
|
7241
|
+
children: [
|
|
7242
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dgfab-ico", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(GamepadIcon, { size: 16 }) }),
|
|
7243
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "dgfab-label", children: "\u8DA3\u5473\u6E38\u620F" })
|
|
7244
|
+
]
|
|
7245
|
+
}
|
|
7246
|
+
);
|
|
7247
|
+
}
|
|
7248
|
+
function GamepadIcon(props) {
|
|
7249
|
+
const size = props && props.size || 16;
|
|
7250
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.3", "aria-hidden": "true", children: [
|
|
7251
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("rect", { x: "1.6", y: "4.6", width: "12.8", height: "6.8", rx: "3.2" }),
|
|
7252
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M4.7 6.8v2.6M3.4 8.1h2.6" }),
|
|
7253
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("circle", { cx: "10.4", cy: "6.9", r: ".95", fill: "currentColor", stroke: "none" }),
|
|
7254
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("circle", { cx: "12.2", cy: "8.7", r: ".95", fill: "currentColor", stroke: "none" })
|
|
7255
|
+
] });
|
|
7256
|
+
}
|
|
7257
|
+
function GamesOverlay() {
|
|
7258
|
+
const winState = useGameWin();
|
|
7259
|
+
const [panelOpen, setPanelOpen2] = (0, import_react18.useState)(panelNav.open);
|
|
7260
|
+
(0, import_react18.useEffect)(() => {
|
|
7261
|
+
setPanelOpen2(panelNav.open);
|
|
7262
|
+
return subscribePanel(() => setPanelOpen2(panelNav.open));
|
|
7263
|
+
}, []);
|
|
7264
|
+
const game = winState.game ? GAMES.find((g) => g.id === winState.game) : null;
|
|
7265
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
7266
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(GameFab, { hidden: winState.open || panelOpen }),
|
|
7267
|
+
winState.open ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(GameWindow, { game, picker: winState.picker }) : null
|
|
7268
|
+
] });
|
|
7269
|
+
}
|
|
7270
|
+
function TaskStatus({ status }) {
|
|
7271
|
+
const task = status.active[0];
|
|
7272
|
+
if (status.loading) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "dgame-task-status loading", children: "\u6B63\u5728\u8BFB\u53D6\u4EFB\u52A1\u72B6\u6001\u2026" });
|
|
7273
|
+
if (!task) return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgame-task-status", children: [
|
|
7274
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}),
|
|
7275
|
+
" \u4EFB\u52A1\u6682\u65F6\u7A7A\u95F2 \xB7 \u6E38\u620F\u968F\u65F6\u53EF\u73A9"
|
|
7276
|
+
] });
|
|
7277
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "dgame-task-status live", children: [
|
|
7278
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}),
|
|
7279
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
|
|
7280
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("strong", { children: [
|
|
7281
|
+
status.active.length,
|
|
7282
|
+
" \u4E2A\u4EFB\u52A1\u8FDB\u884C\u4E2D \xB7 ",
|
|
7283
|
+
phaseLabel2(task.phase)
|
|
7284
|
+
] }),
|
|
7285
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { title: task.title, children: [
|
|
7286
|
+
task.title || "\u5F53\u524D\u4EFB\u52A1",
|
|
7287
|
+
" \xB7 \u5DF2\u8FD0\u884C ",
|
|
7288
|
+
elapsedText(task.elapsed)
|
|
7289
|
+
] })
|
|
7290
|
+
] })
|
|
7291
|
+
] });
|
|
7292
|
+
}
|
|
7293
|
+
function GamesView() {
|
|
7294
|
+
const status = useTaskPulse();
|
|
7295
|
+
const winState = useGameWin();
|
|
7296
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: "dgame", children: [
|
|
7297
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("header", { className: "dgame-hero", children: [
|
|
7298
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
|
|
7299
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "dgame-eyebrow", children: [
|
|
7300
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}),
|
|
7301
|
+
" \u7B49\u5F85\u4E5F\u53EF\u4EE5\u5F88\u597D\u73A9"
|
|
7302
|
+
] }),
|
|
7303
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { children: "\u8DA3\u5473\u6E38\u620F" }),
|
|
7304
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { children: "\u4EFB\u52A1\u5728\u540E\u53F0\u7EE7\u7EED\u6267\u884C\uFF1B\u6E38\u620F\u5728\u5C40\u90E8\u6D6E\u52A8\u7A97\u53E3\u91CC\u8FD0\u884C\uFF0C\u4E0D\u94FA\u6EE1\u5168\u5C4F\u3001\u4E0D\u906E\u6321\u4F1A\u8BDD\u5185\u5BB9\uFF0C\u4E5F\u4E0D\u5F71\u54CD\u6587\u4EF6\u6216\u6B63\u5728\u8FD0\u884C\u7684\u667A\u80FD\u4F53\u3002" })
|
|
7305
|
+
] }),
|
|
7306
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "dgame-planet", "aria-hidden": "true", children: [
|
|
7307
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("i", {}),
|
|
7308
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("b", {})
|
|
7309
|
+
] })
|
|
7310
|
+
] }),
|
|
7311
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TaskStatus, { status }),
|
|
7312
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "dgame-launch-note", children: "\u70B9\u51FB\u5C01\u9762\u5728\u6D6E\u52A8\u7A97\u53E3\u5F00\u73A9\uFF1A\u7A97\u53E3\u53EF\u62D6\u52A8\u3001\u53EF\u8C03\u5927\u5C0F\u3001\u53EF\u6700\u5927\u5316\u6700\u5C0F\u5316\uFF1B\u5C4F\u5E55\u4FA7\u8FB9\u7684\u300C\u8DA3\u5473\u6E38\u620F\u300D\u78C1\u5438\u5165\u53E3\u8D34\u5728\u5DE6\u4FA7\u8FB9\u680F\u53F3\u7F18\uFF08\u4E0D\u6321\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF09\uFF0C\u53EF\u4E0A\u4E0B\u632A\u4F4D\u3001\u5DE6\u53F3\u6362\u8FB9\uFF0C\u4F4D\u7F6E\u90FD\u4F1A\u8BB0\u4F4F\uFF1B\u5341\u6B3E\u5C0F\u6E38\u620F\u90FD\u652F\u6301\u952E\u76D8\uFF08\u80FD\u914D\u6309\u94AE\u7684\u4E5F\u914D\u4E86\u6309\u94AE\uFF09\u64CD\u4F5C\u3002" }),
|
|
7313
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(GameCoverGrid, { currentId: winState.open ? winState.game : null, onLaunch: (id) => {
|
|
7314
|
+
gamesBus.launch(id);
|
|
7315
|
+
setPanelOpen(false);
|
|
7316
|
+
} }),
|
|
7317
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("footer", { className: "dgame-foot", children: "\u6E38\u620F\u4E2D\u6309 Esc \u5173\u95ED\u7A97\u53E3\uFF1B\u6E38\u620F\u4E0D\u53D1\u9001\u4EFB\u4F55\u6D88\u606F\uFF0C\u4E5F\u4E0D\u6539\u52A8\u5DE5\u4F5C\u533A\u3002\u65B9\u5411\u952E / WASD\u3001Enter\u3001\u7A7A\u683C\u3001R\u3001U\u3001P \u7B49\u952E\u968F\u6E38\u620F\u53EF\u7528\uFF1B\u4E94\u5B50\u68CB\u3001\u8C61\u68CB\u652F\u6301\u64A4\u9500/\u91CD\u5F00\u3002" })
|
|
7318
|
+
] });
|
|
7319
|
+
}
|
|
7320
|
+
function GamesHomeStat() {
|
|
7321
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "\u5341\u6B3E\u6D6E\u52A8\u7A97\u53E3\u5C0F\u6E38\u620F \xB7 \u4E94\u5B50\u68CB/\u8C61\u68CB/\u4FC4\u7F57\u65AF\u65B9\u5757/\u63A8\u7BB1\u5B50\u2026\u4EFB\u52A1\u7B49\u5F85\u4E0D\u65E0\u804A" });
|
|
7322
|
+
}
|
|
7323
|
+
var baseCss = `
|
|
7324
|
+
.dgame{--dgame-accent:#a78bfa;--dgame-accent-soft:color-mix(in srgb,var(--dgame-accent) 16%,transparent);display:flex;flex-direction:column;gap:14px;max-width:760px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.dgame h2,.dgame h3,.dgame p{margin:0}.dgame-hero{position:relative;display:flex;justify-content:space-between;gap:18px;overflow:hidden;padding:17px 18px;border:1px solid color-mix(in srgb,var(--dgame-accent) 35%,var(--dsw-alias-border-l1));border-radius:16px;background:radial-gradient(circle at 88% 30%,color-mix(in srgb,#38bdf8 25%,transparent),transparent 28%),radial-gradient(circle at 8% 100%,color-mix(in srgb,#a855f7 24%,transparent),transparent 34%),var(--dsw-alias-bg-layer-1)}.dgame-hero>div{position:relative;z-index:1;display:flex;flex-direction:column;gap:5px;max-width:540px}.dgame-hero h2{font-size:21px;letter-spacing:-.02em}.dgame-hero p,.dgame-foot{color:var(--dsw-alias-label-secondary);font-size:12px}.dgame-eyebrow{display:inline-flex;align-items:center;gap:7px;color:var(--dgame-accent);font-size:11px;font-weight:700}.dgame-eyebrow i,.dgame-task-status>i{width:7px;height:7px;border-radius:50%;background:currentColor;box-shadow:0 0 0 4px currentColor;opacity:.9}.dgame-planet{position:relative;z-index:1;align-self:center;display:block;width:76px;height:76px;flex:none;border-radius:50%;background:radial-gradient(circle at 35% 30%,#e0f2fe 0 4%,#60a5fa 18%,#3730a3 59%,#1e1b4b);box-shadow:0 0 30px color-mix(in srgb,#818cf8 45%,transparent)}.dgame-planet::after{content:'';position:absolute;left:-16px;top:33px;width:106px;height:27px;border:3px solid color-mix(in srgb,#c4b5fd 72%,transparent);border-radius:50%;transform:rotate(-17deg)}.dgame-planet i{position:absolute;right:15px;top:14px;width:9px;height:9px;border-radius:50%;background:#818cf8;box-shadow:-23px 24px 0 5px color-mix(in srgb,#312e81 52%,transparent)}.dgame-planet b{position:absolute;left:-9px;top:9px;width:5px;height:5px;border-radius:50%;background:#fef3c7;box-shadow:58px -20px 0 #fef3c7,76px 43px 0 #bae6fd}.dgame-task-status{display:flex;align-items:center;gap:9px;min-height:40px;padding:9px 12px;border:1px solid var(--dsw-alias-border-l1);border-radius:11px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary)}.dgame-task-status>i{color:var(--dsw-alias-label-tertiary);box-shadow:0 0 0 4px color-mix(in srgb,var(--dsw-alias-label-tertiary) 13%,transparent)}.dgame-task-status.live{color:var(--dgame-accent);border-color:color-mix(in srgb,var(--dgame-accent) 36%,var(--dsw-alias-border-l1));background:var(--dgame-accent-soft)}.dgame-task-status.live>i{box-shadow:0 0 0 4px color-mix(in srgb,var(--dgame-accent) 17%,transparent);animation:dgame-pulse 1.8s ease-in-out infinite}.dgame-task-status div{display:flex;flex:1;min-width:0;flex-direction:column}.dgame-task-status strong{font-size:12px}.dgame-task-status span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:var(--dsw-alias-label-secondary)}.dgame-task-status.loading{color:var(--dsw-alias-label-tertiary)}@keyframes dgame-pulse{50%{transform:scale(.72);opacity:.5}}.dgame-launch-note{color:var(--dsw-alias-label-secondary);font-size:12px}.dgame-game{display:flex;flex-direction:column;gap:11px;padding:15px;border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-1)}.dgame-game-head{display:flex;justify-content:space-between;gap:12px}.dgame-game-head>div:first-child{display:flex;flex-direction:column;gap:3px}.dgame-game h3{font-size:16px}.dgame-game p{font-size:12px;color:var(--dsw-alias-label-secondary)}.dgame-score{display:flex;align-items:flex-end;gap:9px;flex-direction:column;color:var(--dsw-alias-label-tertiary);font-size:11px;white-space:nowrap}.dgame-score strong{color:var(--dgame-accent);font-size:17px;line-height:1}.dgame-score button{min-height:28px;padding:3px 8px;font-size:11px}.dgame-dash-stage{position:relative;align-self:center;width:min(100%,360px);border:1px solid color-mix(in srgb,var(--dgame-accent) 30%,var(--dsw-alias-border-l1));border-radius:13px;overflow:hidden;outline:none;background:radial-gradient(circle at 50% -16%,color-mix(in srgb,#38bdf8 25%,transparent),transparent 42%),linear-gradient(180deg,#10162d,#17142d)}.dgame-dash-stage:focus-visible{box-shadow:0 0 0 3px var(--dgame-accent-soft)}.dgame-dash-grid{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(5,52px);padding:7px;background:repeating-linear-gradient(90deg,transparent 0 calc(33.333% - 1px),color-mix(in srgb,#c4b5fd 15%,transparent) calc(33.333% - 1px) 33.333%)}.dgame-dash-cell{position:relative;display:grid;place-items:center;border-bottom:1px dashed color-mix(in srgb,#c4b5fd 13%,transparent)}.dgame-rock{font-style:normal;color:#fb7185;text-shadow:0 0 12px #f43f5e;font-size:22px;animation:dgame-rock .55s cubic-bezier(.2,.8,.25,1)}.dgame-pilot{position:relative;color:#a5f3fc;font-size:25px;text-shadow:0 0 14px #38bdf8;animation:dgame-pilot .9s ease-in-out infinite alternate}.dgame-pilot::after{content:'';position:absolute;left:46%;top:19px;width:4px;height:18px;background:linear-gradient(#fef3c7,transparent);transform:translateX(-50%)}@keyframes dgame-rock{from{opacity:0;transform:translateY(-13px) scale(.65)}to{opacity:1;transform:none}}@keyframes dgame-pilot{to{transform:translateY(-2px)}}.dgame-over{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:9px;background:color-mix(in srgb,#111827 74%,transparent);backdrop-filter:blur(2px);color:#fef3c7}.dgame-over strong{font-size:17px}.dgame-over button{padding:7px 12px;color:#fef3c7;background:color-mix(in srgb,#f59e0b 25%,transparent);border-color:#f59e0b}.dgame-controls{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:8px;align-self:center;width:min(100%,360px)}.dgame-controls button{min-height:42px;padding:8px 10px;font-weight:600}.dgame-controls span{text-align:center;color:var(--dsw-alias-label-tertiary);font-size:11px}.dgame-reactor{display:grid;grid-template-columns:repeat(3,1fr);gap:9px;align-self:center;width:min(100%,360px);padding:10px;border-radius:13px;background:linear-gradient(135deg,color-mix(in srgb,#312e81 34%,transparent),color-mix(in srgb,#0f172a 45%,transparent));border:1px solid color-mix(in srgb,var(--dgame-accent) 22%,var(--dsw-alias-border-l1))}.dgame-reactor-cell{position:relative;aspect-ratio:1;border:1px solid color-mix(in srgb,#94a3b8 30%,transparent);border-radius:12px;background:#171a33;cursor:pointer;transition:transform .16s ease,background .22s ease,border-color .22s ease,box-shadow .22s ease}.dgame-reactor-cell:hover{transform:translateY(-2px);border-color:var(--dgame-accent)}.dgame-reactor-cell:focus-visible{outline:2px solid var(--dgame-accent);outline-offset:2px}.dgame-reactor-cell i{position:absolute;inset:23%;border-radius:9px;background:#303252;transition:inherit}.dgame-reactor-cell.on{border-color:#67e8f9;background:color-mix(in srgb,#164e63 70%,#0f172a)}.dgame-reactor-cell.on i{background:radial-gradient(circle at 35% 30%,#ecfeff,#67e8f9 36%,#2563eb 78%);box-shadow:0 0 15px color-mix(in srgb,#22d3ee 65%,transparent)}.dgame-reactor-cell.focus{border-color:#a5f3fc;box-shadow:0 0 0 2px var(--dgame-accent) inset,0 0 14px color-mix(in srgb,#22d3ee 48%,transparent)}.dgame-reactor-cell.focus i{box-shadow:0 0 0 2px color-mix(in srgb,#ecfeff 55%,transparent) inset}.dgame-reactor:focus-visible{outline:2px solid var(--dgame-accent);outline-offset:3px}.dgame-reactor-status{align-self:center;min-height:36px;padding:8px 12px;border-radius:9px;color:var(--dsw-alias-label-secondary);font-size:12px}.dgame-reactor-status.solved{color:#a7f3d0;background:color-mix(in srgb,#34d399 13%,transparent)}.dgame-foot{padding:0 2px}.dgame-foot::before{content:'\u2726';margin-right:6px;color:var(--dgame-accent)}
|
|
7325
|
+
.dgame .dgame-score button,.dgame .dgame-controls button,.dgame .dgame-over button,.dgwin .dgame-score button,.dgwin .dgame-controls button,.dgwin .dgame-over button{border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;transition:transform .16s ease,border-color .18s ease,background .18s ease,color .18s ease}.dgame .dgame-score button:hover,.dgame .dgame-controls button:hover,.dgame .dgame-over button:hover,.dgwin .dgame-score button:hover,.dgwin .dgame-controls button:hover,.dgwin .dgame-over button:hover{border-color:var(--dgame-accent);color:var(--dsw-alias-label-primary)}.dgame .dgame-score button:active,.dgame .dgame-controls button:active,.dgame .dgame-over button:active,.dgwin .dgame-score button:active,.dgwin .dgame-controls button:active,.dgwin .dgame-over button:active{transform:scale(.97)}
|
|
7326
|
+
.dgcov-grid{--dgame-accent:#a78bfa;display:flex;gap:10px;flex-wrap:wrap}.dgcov{flex:1;min-width:230px;display:flex;flex-direction:column;gap:8px;padding:11px;border:1px solid var(--dsw-alias-border-l1);border-radius:13px;background:var(--dsw-alias-bg-layer-2);cursor:pointer;font-family:inherit;text-align:left;transition:border-color .16s ease,transform .16s ease}.dgcov:hover{border-color:var(--dgame-accent);transform:translateY(-2px)}.dgcov:focus-visible{outline:2px solid var(--dgame-accent);outline-offset:2px}.dgcov.on{border-color:var(--dgame-accent);box-shadow:0 0 0 1px color-mix(in srgb,var(--dgame-accent) 40%,transparent)}.dgcov-name{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}.dgcov-name i{width:8px;height:8px;border-radius:50%;flex:none}.dgcov-name em{margin-left:auto;font-style:normal;font-size:10px;color:var(--dgame-accent);border:1px solid color-mix(in srgb,var(--dgame-accent) 45%,transparent);border-radius:999px;padding:1px 8px;white-space:nowrap}.dgcov-desc{font-size:11.5px;color:var(--dsw-alias-label-secondary);line-height:1.55}.dgcov-prev{position:relative;display:block;height:96px;border-radius:9px;overflow:hidden;border:1px solid color-mix(in srgb,var(--dgame-accent) 25%,var(--dsw-alias-border-l1))}
|
|
7327
|
+
.dgcov-prev-dash{background:radial-gradient(circle at 70% 20%,color-mix(in srgb,#1d4ed8 30%,transparent),transparent 55%),linear-gradient(180deg,#0d1330,#171233)}.dgcov-prev-dash i{position:absolute;top:-22%;font-style:normal;color:#fb7185;text-shadow:0 0 10px #f43f5e;font-size:15px;animation:dgcov-fall 1.5s linear infinite}.dgcov-prev-dash i::after{content:'\u25C6'}.dgcov-prev-dash i:nth-child(1){left:16%}.dgcov-prev-dash i:nth-child(2){left:50%;animation-delay:-.5s}.dgcov-prev-dash i:nth-child(3){left:82%;animation-delay:-1s}.dgcov-prev-dash b{position:absolute;left:50%;bottom:8px;margin-left:-9px;font-size:17px;color:#a5f3fc;text-shadow:0 0 12px #38bdf8;animation:dgcov-dodge 2.4s ease-in-out infinite}.dgcov-prev-dash b::after{content:'\u25B2'}@keyframes dgcov-fall{to{transform:translateY(140px)}}@keyframes dgcov-dodge{0%,100%{transform:translateX(-46px)}25%,75%{transform:translateX(0)}50%{transform:translateX(46px)}}
|
|
7328
|
+
.dgcov-prev-reactor{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;padding:14px;background:linear-gradient(135deg,#272463,#12102c)}.dgcov-prev-reactor i{display:block;border-radius:9px;background:#191c38;border:1px solid color-mix(in srgb,#ffffff 11%,transparent);animation:dgcov-reactor 3s ease-in-out infinite}.dgcov-prev-reactor i:nth-child(1){animation-delay:0s}.dgcov-prev-reactor i:nth-child(2){animation-delay:-.33s}.dgcov-prev-reactor i:nth-child(3){animation-delay:-.66s}.dgcov-prev-reactor i:nth-child(4){animation-delay:-1s}.dgcov-prev-reactor i:nth-child(5){animation-delay:-1.33s}.dgcov-prev-reactor i:nth-child(6){animation-delay:-1.66s}.dgcov-prev-reactor i:nth-child(7){animation-delay:-2s}.dgcov-prev-reactor i:nth-child(8){animation-delay:-2.33s}.dgcov-prev-reactor i:nth-child(9){animation-delay:-2.66s}@keyframes dgcov-reactor{0%,20%,100%{background:#191c38;box-shadow:none;border-color:color-mix(in srgb,#ffffff 11%,transparent)}40%,64%{background:radial-gradient(circle at 35% 30%,#ecfeff,#67e8f9 40%,#2563eb 85%);box-shadow:0 0 14px color-mix(in srgb,#22d3ee 55%,transparent);border-color:#67e8f9}}
|
|
7329
|
+
.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)}
|
|
7330
|
+
.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)}}
|
|
7331
|
+
.dgwin-min{height:auto!important;border-radius:12px}.dgwin-min .dgwin-body{display:none}.dgwin-min .dgwin-resize{display:none}
|
|
7332
|
+
.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}
|
|
7333
|
+
.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}
|
|
7334
|
+
/* \u5C0F\u7A97\u5185\u5BB9\u81EA\u9002\u5E94\uFF1A\u9AD8\u5EA6\u968F\u5185\u5BB9\uFF08\u4E0D\u88C1\u5207\u3001\u4E0D\u6EDA\u52A8\uFF09\uFF0C\u6E38\u620F\u677F\u6309\u5C4F\u5E55\u53EF\u7528\u9AD8\u5EA6\u7B49\u6BD4\u7F29\u653E */
|
|
7335
|
+
.dgwin-normal{max-height:calc(100vh - 12px)}
|
|
7336
|
+
.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))}
|
|
7337
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-gomoku-board{width:min(100%,390px,calc(100vh - 245px))}
|
|
7338
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-xiangqi-board{width:min(100%,370px,calc((100vh - 240px) * .9))}
|
|
7339
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-breakout{width:min(100%,410px,calc((100vh - 235px) * .89))}
|
|
7340
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-tetris-board{width:min(100%,215px,calc((100vh - 270px) / 2))}
|
|
7341
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-dash-stage{width:min(100%,430px,calc((100vh - 215px) * 1.24))}
|
|
7342
|
+
.dgwin:not(.dgwin-max):not(.dgwin-min) .dgame-racer{width:min(100%,400px,calc((100vh - 235px) * .71))}
|
|
7343
|
+
.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}
|
|
7344
|
+
.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)}
|
|
7345
|
+
.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}
|
|
7346
|
+
@keyframes dgentry-in{from{opacity:0}}
|
|
7347
|
+
@media (max-width:680px){.dgame{gap:12px;font-size:14px}.dgame-hero{padding:15px}.dgame-hero h2{font-size:20px}.dgame-planet{width:58px;height:58px}.dgame-planet::after{left:-12px;top:25px;width:80px;height:20px}.dgame-game{padding:12px}.dgame-dash-grid{grid-template-rows:repeat(5,48px)}.dgame-controls{grid-template-columns:1fr 1fr}.dgame-controls span{grid-column:1 / -1;grid-row:2}.dgame-reactor{gap:8px}.dgcov-grid{flex-direction:column}.dgcov{min-width:0}.dgfab{width:32px}.dgwin-title span,.dgwin-task{display:none}.dgwin-bar{gap:7px;padding:8px 10px}.dgwin-btn{padding:6px 8px}.dgwin-body{padding:10px}}
|
|
7348
|
+
@media (prefers-reduced-motion:reduce){.dgame *,.dgcov *,.dgfab,.dgwin,.dgwin-picker *{animation:none!important;transition:none!important}}
|
|
7349
|
+
`;
|
|
7350
|
+
var dgcovGroupCss = `
|
|
7351
|
+
.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}
|
|
7352
|
+
.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}
|
|
7353
|
+
.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}
|
|
7354
|
+
.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)}}
|
|
7355
|
+
.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}
|
|
7356
|
+
.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)}}
|
|
7357
|
+
.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%)}
|
|
7358
|
+
.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%)}
|
|
7359
|
+
.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%}
|
|
7360
|
+
|
|
7361
|
+
`;
|
|
7362
|
+
var css3 = [baseCss, tetrisGame.css, sokobanGame.css, gomokuGame.css, xiangqiGame.css, snakeGame.css, breakoutGame.css, racerGame.css, tankGame.css, dgcovGroupCss].filter(Boolean).join("\n");
|
|
7363
|
+
var feature7 = {
|
|
7364
|
+
id: "games",
|
|
7365
|
+
name: "\u8DA3\u5473\u6E38\u620F",
|
|
7366
|
+
order: 135,
|
|
7367
|
+
accent: "#a78bfa",
|
|
7368
|
+
description: "\u4EFB\u52A1\u7B49\u5F85\u65F6\u53EF\u73A9\u7684\u5341\u6B3E\u5C0F\u6E38\u620F\uFF1A\u4E94\u5B50\u68CB\u3001\u4E2D\u56FD\u8C61\u68CB\uFF08\u4F60 vs AI\uFF09\u3001\u4FC4\u7F57\u65AF\u65B9\u5757\u3001\u63A8\u7BB1\u5B50\u3001\u8D2A\u5403\u86C7\u3001\u6253\u7816\u5757\u3001\u6781\u901F\u8D5B\u8F66\u3001\u5766\u514B\u5927\u6218\u3001\u661F\u9645\u8EB2\u907F\u3001\u53CD\u5E94\u5806\u70B9\u4EAE\u3002\u6D6E\u52A8\u7A97\u53E3\u8FD0\u884C\uFF08\u53EF\u62D6\u52A8\u3001\u53EF\u8C03\u5927\u5C0F\u3001\u53EF\u6700\u5927\u5316\u6700\u5C0F\u5316\uFF0C\u4E0D\u906E\u6321\u4F1A\u8BDD\uFF09\uFF0C\u5C4F\u5E55\u4FA7\u8FB9\u78C1\u5438\u5FEB\u6377\u5165\u53E3\u8D34\u5728\u5DE6\u4FA7\u8FB9\u680F\u53F3\u7F18\uFF08\u4E0D\u6321\u5DE5\u4F5C\u533A\u76EE\u5F55\uFF09\uFF0C\u53EF\u4E0A\u4E0B\u632A\u4F4D\u3001\u5DE6\u53F3\u6362\u8FB9\uFF1B\u5747\u652F\u6301\u952E\u76D8",
|
|
7369
|
+
defaultEnabled: true,
|
|
7370
|
+
css: css3,
|
|
7371
|
+
View: GamesView,
|
|
7372
|
+
HomeStat: GamesHomeStat,
|
|
7373
|
+
Overlay: GamesOverlay
|
|
7374
|
+
};
|
|
7375
|
+
|
|
3923
7376
|
// src/client.jsx
|
|
3924
|
-
var DOCK_VERSION = "0.
|
|
3925
|
-
var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6];
|
|
7377
|
+
var DOCK_VERSION = "0.8.0";
|
|
7378
|
+
var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6, feature7];
|
|
3926
7379
|
var PLANNED_FEATURES = [];
|
|
3927
7380
|
var PLANNED_NOTES = {};
|
|
3928
7381
|
var externalDefs = [];
|
|
@@ -4124,8 +7577,8 @@ function scheduleInitialCss() {
|
|
|
4124
7577
|
}
|
|
4125
7578
|
}
|
|
4126
7579
|
function useExternalVersion() {
|
|
4127
|
-
const [, bump] =
|
|
4128
|
-
|
|
7580
|
+
const [, bump] = import_react19.default.useReducer((n) => n + 1, 0);
|
|
7581
|
+
import_react19.default.useEffect(() => {
|
|
4129
7582
|
externalListeners.add(bump);
|
|
4130
7583
|
return () => {
|
|
4131
7584
|
externalListeners.delete(bump);
|
|
@@ -4135,44 +7588,44 @@ function useExternalVersion() {
|
|
|
4135
7588
|
var lastGeom = { x: null, y: null, w: null, h: null };
|
|
4136
7589
|
function DockIcon(props) {
|
|
4137
7590
|
const size = props && props.size || 16;
|
|
4138
|
-
return
|
|
7591
|
+
return import_react19.default.createElement(
|
|
4139
7592
|
"svg",
|
|
4140
7593
|
{ width: size, height: size, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.3, "aria-hidden": true },
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
7594
|
+
import_react19.default.createElement("rect", { x: 2.5, y: 2.5, width: 4.4, height: 4.4, rx: 1.2 }),
|
|
7595
|
+
import_react19.default.createElement("rect", { x: 9, y: 2.5, width: 4.4, height: 4.4, rx: 1.2 }),
|
|
7596
|
+
import_react19.default.createElement("rect", { x: 2.5, y: 9, width: 4.4, height: 4.4, rx: 1.2 }),
|
|
7597
|
+
import_react19.default.createElement("rect", { x: 9, y: 9, width: 4.4, height: 4.4, rx: 1.2 })
|
|
4145
7598
|
);
|
|
4146
7599
|
}
|
|
4147
7600
|
function DockEntry(props) {
|
|
4148
7601
|
const wide = !!props.wide;
|
|
4149
|
-
const [open, setOpen] =
|
|
4150
|
-
|
|
4151
|
-
return
|
|
7602
|
+
const [open, setOpen] = import_react19.default.useState(panelNav.open);
|
|
7603
|
+
import_react19.default.useEffect(() => subscribePanel(() => setOpen(panelNav.open)), []);
|
|
7604
|
+
return import_react19.default.createElement("button", {
|
|
4152
7605
|
type: "button",
|
|
4153
7606
|
className: "docke2-btn" + (open ? " docke2-on" : "") + (wide ? "" : " docke2-rail"),
|
|
4154
7607
|
title: "\u529F\u80FD\u575E",
|
|
4155
7608
|
"aria-label": "\u529F\u80FD\u575E",
|
|
4156
7609
|
"aria-expanded": open,
|
|
4157
7610
|
onClick: () => setPanelOpen(!open)
|
|
4158
|
-
},
|
|
7611
|
+
}, import_react19.default.createElement(DockIcon, { size: wide ? 16 : 18 }), wide ? import_react19.default.createElement("span", { className: "docke2-label" }, "\u529F\u80FD\u575E") : null);
|
|
4159
7612
|
}
|
|
4160
7613
|
function DockModal() {
|
|
4161
|
-
const [nav, setNav] =
|
|
4162
|
-
|
|
7614
|
+
const [nav, setNav] = import_react19.default.useState({ open: panelNav.open, active: panelNav.active, params: panelNav.params });
|
|
7615
|
+
import_react19.default.useEffect(() => subscribePanel(() => setNav({ open: panelNav.open, active: panelNav.active, params: panelNav.params })), []);
|
|
4163
7616
|
const open = nav.open || typeof document === "undefined";
|
|
4164
7617
|
const active = nav.active;
|
|
4165
7618
|
const setActive = navigatePanel;
|
|
4166
7619
|
const navParams = nav.params;
|
|
4167
|
-
const [, force] =
|
|
7620
|
+
const [, force] = import_react19.default.useReducer((n) => n + 1, 0);
|
|
4168
7621
|
useExternalVersion();
|
|
4169
|
-
const [win, setWin] =
|
|
4170
|
-
const dlgRef =
|
|
4171
|
-
const contentRef =
|
|
4172
|
-
|
|
7622
|
+
const [win, setWin] = import_react19.default.useState(() => ({ mode: "normal", x: null, y: null, w: null, h: null }));
|
|
7623
|
+
const dlgRef = import_react19.default.useRef(null);
|
|
7624
|
+
const contentRef = import_react19.default.useRef(null);
|
|
7625
|
+
import_react19.default.useEffect(() => {
|
|
4173
7626
|
if (lastGeom.w) setWin({ mode: "normal", x: lastGeom.x, y: lastGeom.y, w: lastGeom.w, h: lastGeom.h });
|
|
4174
7627
|
}, []);
|
|
4175
|
-
|
|
7628
|
+
import_react19.default.useEffect(() => {
|
|
4176
7629
|
if (open && contentRef.current) contentRef.current.scrollTop = 0;
|
|
4177
7630
|
}, [active, open]);
|
|
4178
7631
|
function beginDrag(e, type) {
|
|
@@ -4180,9 +7633,9 @@ function DockModal() {
|
|
|
4180
7633
|
if (type === "move" && e.target && e.target.closest && e.target.closest("button,select,input")) return;
|
|
4181
7634
|
const node = dlgRef.current;
|
|
4182
7635
|
if (!node || typeof window === "undefined") return;
|
|
4183
|
-
const
|
|
7636
|
+
const rect2 = node.getBoundingClientRect();
|
|
4184
7637
|
const startX = e.clientX, startY = e.clientY;
|
|
4185
|
-
const origin = { left:
|
|
7638
|
+
const origin = { left: rect2.left, top: rect2.top, w: rect2.width, h: rect2.height };
|
|
4186
7639
|
const clampX = (v) => Math.min(Math.max(8, v), window.innerWidth - origin.w - 8);
|
|
4187
7640
|
const clampY = (v) => Math.min(Math.max(8, v), window.innerHeight - origin.h - 8);
|
|
4188
7641
|
const onMove = (ev) => {
|
|
@@ -4210,13 +7663,13 @@ function DockModal() {
|
|
|
4210
7663
|
const mod = isHome ? null : MODULES.find((m) => m.id === active) || MODULES[0];
|
|
4211
7664
|
const st = mod ? stateOf(mod.id) : null;
|
|
4212
7665
|
const View = mod ? mod.View : null;
|
|
4213
|
-
const viewNode = mod && View ?
|
|
7666
|
+
const viewNode = mod && View ? import_react19.default.createElement(
|
|
4214
7667
|
"div",
|
|
4215
7668
|
{ className: "dockm-view" },
|
|
4216
|
-
|
|
4217
|
-
mod.external ? FeatureBoundary :
|
|
7669
|
+
import_react19.default.createElement(
|
|
7670
|
+
mod.external ? FeatureBoundary : import_react19.default.Fragment,
|
|
4218
7671
|
null,
|
|
4219
|
-
|
|
7672
|
+
import_react19.default.createElement(View, { ctx: ctxRef.current, feature: mod, params: navParams })
|
|
4220
7673
|
)
|
|
4221
7674
|
) : null;
|
|
4222
7675
|
const enabledCount = MODULES.filter((m) => {
|
|
@@ -4230,10 +7683,10 @@ function DockModal() {
|
|
|
4230
7683
|
width: win.w != null ? win.w : void 0,
|
|
4231
7684
|
height: win.mode === "min" ? "auto" : win.h != null ? win.h : void 0
|
|
4232
7685
|
} : null;
|
|
4233
|
-
return
|
|
7686
|
+
return import_react19.default.createElement(
|
|
4234
7687
|
"div",
|
|
4235
7688
|
{ className: "dockm-backdrop", onClick: () => setPanelOpen(false) },
|
|
4236
|
-
|
|
7689
|
+
import_react19.default.createElement(
|
|
4237
7690
|
"div",
|
|
4238
7691
|
{
|
|
4239
7692
|
className: "dockm-dialog" + (win.mode === "max" ? " dockm-max" : "") + (win.mode === "min" ? " dockm-min" : ""),
|
|
@@ -4241,41 +7694,41 @@ function DockModal() {
|
|
|
4241
7694
|
ref: dlgRef,
|
|
4242
7695
|
onClick: (e) => e.stopPropagation()
|
|
4243
7696
|
},
|
|
4244
|
-
|
|
7697
|
+
import_react19.default.createElement(
|
|
4245
7698
|
"div",
|
|
4246
7699
|
{
|
|
4247
7700
|
className: "dockm-head",
|
|
4248
7701
|
onPointerDown: (e) => beginDrag(e, "move"),
|
|
4249
7702
|
onDoubleClick: () => setWin((s) => Object.assign({}, s, { mode: s.mode === "max" ? "normal" : "max" }))
|
|
4250
7703
|
},
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
7704
|
+
import_react19.default.createElement(DockIcon, null),
|
|
7705
|
+
import_react19.default.createElement("span", { className: "dockm-title" }, "\u529F\u80FD\u575E"),
|
|
7706
|
+
import_react19.default.createElement("span", { className: "dockm-sub" }, "dsh-dock \xB7 \u4E5F\u53EF\u5728 \u8BBE\u7F6E \u2192 \u529F\u80FD\u575E \u6253\u5F00\u7BA1\u7406\u9875"),
|
|
7707
|
+
import_react19.default.createElement(
|
|
4255
7708
|
"span",
|
|
4256
7709
|
{ className: "dockm-ctrls" },
|
|
4257
|
-
|
|
7710
|
+
import_react19.default.createElement("button", {
|
|
4258
7711
|
type: "button",
|
|
4259
7712
|
className: "dockm-win",
|
|
4260
7713
|
title: win.mode === "min" ? "\u8FD8\u539F" : "\u6700\u5C0F\u5316",
|
|
4261
7714
|
onClick: () => setWin((s) => Object.assign({}, s, { mode: s.mode === "min" ? "normal" : "min" }))
|
|
4262
7715
|
}, "\u2581"),
|
|
4263
|
-
|
|
7716
|
+
import_react19.default.createElement("button", {
|
|
4264
7717
|
type: "button",
|
|
4265
7718
|
className: "dockm-win",
|
|
4266
7719
|
title: win.mode === "max" ? "\u8FD8\u539F" : "\u6700\u5927\u5316",
|
|
4267
7720
|
onClick: () => setWin((s) => Object.assign({}, s, { mode: s.mode === "max" ? "normal" : "max" }))
|
|
4268
7721
|
}, win.mode === "max" ? "\u2750" : "\u25A2"),
|
|
4269
|
-
|
|
7722
|
+
import_react19.default.createElement("button", { type: "button", className: "dockm-close", "aria-label": "\u5173\u95ED", title: "\u5173\u95ED", onClick: () => setPanelOpen(false) }, "\u2715")
|
|
4270
7723
|
)
|
|
4271
7724
|
),
|
|
4272
|
-
|
|
7725
|
+
import_react19.default.createElement(
|
|
4273
7726
|
"div",
|
|
4274
7727
|
{ className: "dockm-body" },
|
|
4275
|
-
|
|
7728
|
+
import_react19.default.createElement(
|
|
4276
7729
|
"nav",
|
|
4277
7730
|
{ className: "dockm-nav", "aria-label": "\u529F\u80FD\u6A21\u5757" },
|
|
4278
|
-
|
|
7731
|
+
import_react19.default.createElement(
|
|
4279
7732
|
"button",
|
|
4280
7733
|
{
|
|
4281
7734
|
type: "button",
|
|
@@ -4283,10 +7736,10 @@ function DockModal() {
|
|
|
4283
7736
|
className: "dockm-nav-item" + (isHome ? " on" : ""),
|
|
4284
7737
|
onClick: () => setActive("home")
|
|
4285
7738
|
},
|
|
4286
|
-
|
|
4287
|
-
|
|
7739
|
+
import_react19.default.createElement("span", { className: "dockm-navhome" }, import_react19.default.createElement(DockIcon, null)),
|
|
7740
|
+
import_react19.default.createElement("span", null, "\u9996\u9875")
|
|
4288
7741
|
),
|
|
4289
|
-
MODULES.map((m) =>
|
|
7742
|
+
MODULES.map((m) => import_react19.default.createElement(
|
|
4290
7743
|
"button",
|
|
4291
7744
|
{
|
|
4292
7745
|
type: "button",
|
|
@@ -4294,42 +7747,42 @@ function DockModal() {
|
|
|
4294
7747
|
className: "dockm-nav-item" + (m.id === active ? " on" : ""),
|
|
4295
7748
|
onClick: () => setActive(m.id)
|
|
4296
7749
|
},
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
m.planned ?
|
|
4300
|
-
m.external ?
|
|
7750
|
+
import_react19.default.createElement("span", { className: "dockm-dot", style: { background: m.accent } }),
|
|
7751
|
+
import_react19.default.createElement("span", null, m.name),
|
|
7752
|
+
m.planned ? import_react19.default.createElement("span", { className: "dockm-badge" }, "\u89C4\u5212\u4E2D") : null,
|
|
7753
|
+
m.external ? import_react19.default.createElement("span", { className: "dockm-badge" }, "\u5916\u90E8") : null
|
|
4301
7754
|
))
|
|
4302
7755
|
),
|
|
4303
|
-
|
|
7756
|
+
import_react19.default.createElement(
|
|
4304
7757
|
"div",
|
|
4305
7758
|
{ className: "dockm-content", ref: contentRef },
|
|
4306
|
-
|
|
7759
|
+
import_react19.default.createElement(
|
|
4307
7760
|
"div",
|
|
4308
7761
|
{ className: "dockm-content-head" },
|
|
4309
|
-
|
|
7762
|
+
import_react19.default.createElement(
|
|
4310
7763
|
"div",
|
|
4311
7764
|
{ className: "dockm-name" },
|
|
4312
|
-
isHome ?
|
|
7765
|
+
isHome ? import_react19.default.createElement("span", { className: "dockm-navhome" }, import_react19.default.createElement(DockIcon, null)) : import_react19.default.createElement("span", { className: "dockm-dot", style: { background: mod.accent } }),
|
|
4313
7766
|
isHome ? "\u9996\u9875" : mod.name,
|
|
4314
|
-
!isHome && mod.planned ?
|
|
4315
|
-
!isHome && mod.external ?
|
|
7767
|
+
!isHome && mod.planned ? import_react19.default.createElement("span", { className: "dockm-badge" }, "\u89C4\u5212\u4E2D") : null,
|
|
7768
|
+
!isHome && mod.external ? import_react19.default.createElement("span", { className: "dockm-badge" }, "\u5916\u90E8\u5305" + (mod.package ? " \xB7 " + mod.package : "")) : null
|
|
4316
7769
|
),
|
|
4317
|
-
|
|
7770
|
+
import_react19.default.createElement(
|
|
4318
7771
|
"div",
|
|
4319
7772
|
{ className: "dockm-desc" },
|
|
4320
7773
|
isHome ? "\u6240\u6709\u5B50\u529F\u80FD\u603B\u63FD\uFF1A\u8FD0\u884C\u72B6\u6001\u3001\u6982\u8981\u4E0E\u5FEB\u6377\u5F00\u5173\uFF0C\u70B9\u51FB\u5361\u7247\u8FDB\u5165\u5BF9\u5E94\u529F\u80FD\u9875\u3002" : mod.description
|
|
4321
7774
|
)
|
|
4322
7775
|
),
|
|
4323
|
-
isHome ?
|
|
4324
|
-
|
|
7776
|
+
isHome ? import_react19.default.createElement("div", { className: "dockm-view" }, import_react19.default.createElement(HomeView, { ctx: ctxRef.current, onOpen: setActive, onToggle: force })) : mod.planned ? import_react19.default.createElement("div", { className: "dockm-note" }, PLANNED_NOTES[mod.id] || "\u5F85\u63A5\u5165\uFF1A\u89C1 README \u8DEF\u7EBF\u56FE") : st && st.enabled && viewNode ? viewNode : st && st.error ? import_react19.default.createElement("div", { className: "dockm-note dockm-err" }, "\u529F\u80FD\u51FA\u9519\uFF1A" + st.error) : import_react19.default.createElement("div", { className: "dockm-note" }, "\u8BE5\u529F\u80FD\u5F53\u524D\u4E3A\u505C\u7528\u72B6\u6001\uFF08\u5F00\u5173\u5DF2\u6301\u4E45\u5316\uFF0C\u91CD\u542F\u540E\u4FDD\u6301\uFF09"),
|
|
7777
|
+
import_react19.default.createElement(
|
|
4325
7778
|
"div",
|
|
4326
7779
|
{ className: "dockm-foot" },
|
|
4327
|
-
|
|
4328
|
-
!isHome && mod && !mod.planned && st ?
|
|
7780
|
+
import_react19.default.createElement("span", null, isHome ? "\u529F\u80FD\u575E v" + DOCK_VERSION + " \xB7 \u5171 " + MODULES.length + " \u4E2A\u529F\u80FD\u6A21\u5757\uFF0C" + enabledCount + " \u4E2A\u5DF2\u542F\u7528" : "\u529F\u80FD\u575E v" + DOCK_VERSION + " \xB7 \u65B0\u529F\u80FD\u6309\u8DEF\u7EBF\u56FE\u8FFD\u52A0"),
|
|
7781
|
+
!isHome && mod && !mod.planned && st ? import_react19.default.createElement(
|
|
4329
7782
|
"span",
|
|
4330
7783
|
{ className: "dockm-foot-sw" },
|
|
4331
|
-
|
|
4332
|
-
|
|
7784
|
+
import_react19.default.createElement("span", { className: "dockm-foot-swlabel" + (st.enabled ? " on" : "") }, st.enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u505C\u7528"),
|
|
7785
|
+
import_react19.default.createElement("button", {
|
|
4333
7786
|
type: "button",
|
|
4334
7787
|
className: "dock-sw" + (st.enabled ? " on" : ""),
|
|
4335
7788
|
role: "switch",
|
|
@@ -4342,11 +7795,11 @@ function DockModal() {
|
|
|
4342
7795
|
}
|
|
4343
7796
|
})
|
|
4344
7797
|
) : null,
|
|
4345
|
-
!isHome && mod && typeof mod.Chip === "function" ?
|
|
7798
|
+
!isHome && mod && typeof mod.Chip === "function" ? import_react19.default.createElement(
|
|
4346
7799
|
"span",
|
|
4347
7800
|
{ className: "dockm-foot-sw" },
|
|
4348
|
-
|
|
4349
|
-
|
|
7801
|
+
import_react19.default.createElement("span", { className: "dockm-foot-swlabel" + (chipShown(mod.id) ? " on" : "") }, "\u4F1A\u8BDD\u9875\u5C0F\u63A7\u4EF6"),
|
|
7802
|
+
import_react19.default.createElement("button", {
|
|
4350
7803
|
type: "button",
|
|
4351
7804
|
className: "dock-sw" + (chipShown(mod.id) ? " on" : ""),
|
|
4352
7805
|
role: "switch",
|
|
@@ -4362,30 +7815,30 @@ function DockModal() {
|
|
|
4362
7815
|
)
|
|
4363
7816
|
)
|
|
4364
7817
|
),
|
|
4365
|
-
win.mode === "normal" ?
|
|
7818
|
+
win.mode === "normal" ? import_react19.default.createElement("div", { className: "dockm-resize", onPointerDown: (e) => beginDrag(e, "size") }) : null
|
|
4366
7819
|
)
|
|
4367
7820
|
);
|
|
4368
7821
|
}
|
|
4369
7822
|
function HomeView(props) {
|
|
4370
7823
|
const ctx = props && props.ctx;
|
|
4371
|
-
const [, force] =
|
|
7824
|
+
const [, force] = import_react19.default.useReducer((n) => n + 1, 0);
|
|
4372
7825
|
useExternalVersion();
|
|
4373
7826
|
const open = (id) => {
|
|
4374
7827
|
if (props && typeof props.onOpen === "function") props.onOpen(id);
|
|
4375
7828
|
};
|
|
4376
|
-
return
|
|
7829
|
+
return import_react19.default.createElement(
|
|
4377
7830
|
"div",
|
|
4378
7831
|
{ className: "dockh-grid" },
|
|
4379
7832
|
allModules().map((m) => {
|
|
4380
7833
|
const st = stateOf(m.id);
|
|
4381
7834
|
const enabled = !!(st && st.enabled);
|
|
4382
7835
|
const Stat = m.HomeStat;
|
|
4383
|
-
const statNode = m.planned ?
|
|
4384
|
-
m.external ? FeatureBoundary :
|
|
7836
|
+
const statNode = m.planned ? import_react19.default.createElement("span", null, PLANNED_NOTES[m.id] || "\u5F85\u63A5\u5165\uFF1A\u89C1 README \u8DEF\u7EBF\u56FE") : enabled && Stat ? import_react19.default.createElement(
|
|
7837
|
+
m.external ? FeatureBoundary : import_react19.default.Fragment,
|
|
4385
7838
|
null,
|
|
4386
|
-
|
|
4387
|
-
) :
|
|
4388
|
-
return
|
|
7839
|
+
import_react19.default.createElement(Stat, { ctx })
|
|
7840
|
+
) : import_react19.default.createElement("span", null, "\u5DF2\u505C\u7528\uFF0C\u542F\u7528\u540E\u5728\u6B64\u5C55\u793A\u8FD0\u884C\u6982\u8981");
|
|
7841
|
+
return import_react19.default.createElement(
|
|
4389
7842
|
"div",
|
|
4390
7843
|
{
|
|
4391
7844
|
key: m.id,
|
|
@@ -4400,21 +7853,21 @@ function HomeView(props) {
|
|
|
4400
7853
|
}
|
|
4401
7854
|
}
|
|
4402
7855
|
},
|
|
4403
|
-
|
|
7856
|
+
import_react19.default.createElement(
|
|
4404
7857
|
"div",
|
|
4405
7858
|
{ className: "dockh-head" },
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
m.external ?
|
|
7859
|
+
import_react19.default.createElement("span", { className: "dockm-dot", style: { background: m.accent } }),
|
|
7860
|
+
import_react19.default.createElement("span", { className: "dockh-name" }, m.name),
|
|
7861
|
+
m.external ? import_react19.default.createElement("span", { className: "dockh-badge", title: m.package || void 0 }, "\u5916\u90E8") : null,
|
|
4409
7862
|
// 状态标识:圆点 + 文字(纯展示,与开关视觉区分)
|
|
4410
|
-
|
|
7863
|
+
import_react19.default.createElement(
|
|
4411
7864
|
"span",
|
|
4412
7865
|
{ className: "dockh-status" + (m.planned ? " plan" : enabled ? "" : " off") },
|
|
4413
|
-
|
|
4414
|
-
|
|
7866
|
+
import_react19.default.createElement("span", { className: "dockh-sdot" }),
|
|
7867
|
+
import_react19.default.createElement("span", null, m.planned ? "\u89C4\u5212\u4E2D" : enabled ? "\u8FD0\u884C\u4E2D" : "\u5DF2\u505C\u7528")
|
|
4415
7868
|
),
|
|
4416
7869
|
// 启停开关(规划中的功能不显示)
|
|
4417
|
-
m.planned ? null :
|
|
7870
|
+
m.planned ? null : import_react19.default.createElement("button", {
|
|
4418
7871
|
type: "button",
|
|
4419
7872
|
className: "dock-sw" + (enabled ? " on" : ""),
|
|
4420
7873
|
role: "switch",
|
|
@@ -4429,12 +7882,12 @@ function HomeView(props) {
|
|
|
4429
7882
|
}
|
|
4430
7883
|
})
|
|
4431
7884
|
),
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
7885
|
+
import_react19.default.createElement("div", { className: "dockh-desc" }, m.description),
|
|
7886
|
+
import_react19.default.createElement("div", { className: "dockh-stat" }, statNode),
|
|
7887
|
+
import_react19.default.createElement(
|
|
4435
7888
|
"div",
|
|
4436
7889
|
{ className: "dockh-foot" },
|
|
4437
|
-
|
|
7890
|
+
import_react19.default.createElement("span", { className: "dockh-go" }, "\u67E5\u770B\u8BE6\u60C5 \u2192")
|
|
4438
7891
|
)
|
|
4439
7892
|
);
|
|
4440
7893
|
})
|
|
@@ -4442,16 +7895,16 @@ function HomeView(props) {
|
|
|
4442
7895
|
}
|
|
4443
7896
|
function DockPanel() {
|
|
4444
7897
|
const ctx = ctxRef.current;
|
|
4445
|
-
const [, force] =
|
|
7898
|
+
const [, force] = import_react19.default.useReducer((n) => n + 1, 0);
|
|
4446
7899
|
useExternalVersion();
|
|
4447
7900
|
const toggle = (id) => {
|
|
4448
7901
|
toggleFeature(id);
|
|
4449
7902
|
force();
|
|
4450
7903
|
};
|
|
4451
|
-
return
|
|
7904
|
+
return import_react19.default.createElement(
|
|
4452
7905
|
"div",
|
|
4453
7906
|
{ className: "dock-root" },
|
|
4454
|
-
|
|
7907
|
+
import_react19.default.createElement(
|
|
4455
7908
|
"div",
|
|
4456
7909
|
{ className: "dock-intro" },
|
|
4457
7910
|
"\u529F\u80FD\u575E\uFF08dsh-dock\uFF09\xB7 \u6240\u6709\u5C0F\u529F\u80FD\u96C6\u4E2D\u5728\u8FD9\u4E00\u4E2A\u9762\u677F\u91CC\u7BA1\u7406\u3002v0.4.0 \u8D77\u6BCF\u4E2A\u529F\u80FD\u662F\u72EC\u7ACB\u6A21\u5757\uFF08features/<id>/\uFF09\uFF0C",
|
|
@@ -4460,25 +7913,25 @@ function DockPanel() {
|
|
|
4460
7913
|
allModules().map((f) => {
|
|
4461
7914
|
const st = stateOf(f.id);
|
|
4462
7915
|
const View = f.View;
|
|
4463
|
-
const viewNode = !f.planned && st.enabled && View ?
|
|
7916
|
+
const viewNode = !f.planned && st.enabled && View ? import_react19.default.createElement(
|
|
4464
7917
|
"div",
|
|
4465
7918
|
{ className: "dock-body" },
|
|
4466
|
-
|
|
4467
|
-
f.external ? FeatureBoundary :
|
|
7919
|
+
import_react19.default.createElement(
|
|
7920
|
+
f.external ? FeatureBoundary : import_react19.default.Fragment,
|
|
4468
7921
|
null,
|
|
4469
|
-
|
|
7922
|
+
import_react19.default.createElement(View, { ctx, feature: f })
|
|
4470
7923
|
)
|
|
4471
7924
|
) : null;
|
|
4472
|
-
return
|
|
7925
|
+
return import_react19.default.createElement(
|
|
4473
7926
|
"div",
|
|
4474
7927
|
{ className: "dock-card", key: f.id },
|
|
4475
|
-
|
|
7928
|
+
import_react19.default.createElement(
|
|
4476
7929
|
"div",
|
|
4477
7930
|
{ className: "dock-card-head" },
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
f.planned ?
|
|
7931
|
+
import_react19.default.createElement("span", { className: "dock-dot" + (st.error ? " err" : st.enabled ? " on" : "") }),
|
|
7932
|
+
import_react19.default.createElement("span", { className: "dock-name" }, f.name),
|
|
7933
|
+
import_react19.default.createElement("span", { className: "dock-desc" }, f.description + (f.external ? "\uFF08\u6765\u81EA\u5916\u90E8\u5305" + (f.package ? " " + f.package : "") + "\uFF09" : "")),
|
|
7934
|
+
f.planned ? import_react19.default.createElement("span", { className: "dock-badge" }, "\u89C4\u5212\u4E2D") : import_react19.default.createElement("button", {
|
|
4482
7935
|
type: "button",
|
|
4483
7936
|
className: "dock-sw" + (st.enabled ? " on" : ""),
|
|
4484
7937
|
role: "switch",
|
|
@@ -4487,11 +7940,11 @@ function DockPanel() {
|
|
|
4487
7940
|
title: st.enabled ? "\u505C\u7528\u300C" + f.name + "\u300D" : "\u542F\u7528\u300C" + f.name + "\u300D",
|
|
4488
7941
|
onClick: () => toggle(f.id)
|
|
4489
7942
|
}),
|
|
4490
|
-
!f.planned && typeof f.Chip === "function" ?
|
|
7943
|
+
!f.planned && typeof f.Chip === "function" ? import_react19.default.createElement(
|
|
4491
7944
|
"span",
|
|
4492
7945
|
{ className: "dockm-foot-sw" },
|
|
4493
|
-
|
|
4494
|
-
|
|
7946
|
+
import_react19.default.createElement("span", { className: "dockm-foot-swlabel" + (chipShown(f.id) ? " on" : "") }, "\u4F1A\u8BDD\u9875\u5C0F\u63A7\u4EF6"),
|
|
7947
|
+
import_react19.default.createElement("button", {
|
|
4495
7948
|
type: "button",
|
|
4496
7949
|
className: "dock-sw" + (chipShown(f.id) ? " on" : ""),
|
|
4497
7950
|
role: "switch",
|
|
@@ -4505,19 +7958,19 @@ function DockPanel() {
|
|
|
4505
7958
|
})
|
|
4506
7959
|
) : null
|
|
4507
7960
|
),
|
|
4508
|
-
f.planned ?
|
|
7961
|
+
f.planned ? import_react19.default.createElement("div", { className: "dock-body" }, PLANNED_NOTES[f.id] || "\u5F85\u63A5\u5165\uFF1A\u89C1 README \u8DEF\u7EBF\u56FE") : st.error ? import_react19.default.createElement("div", { className: "dock-body dockm-err" }, "\u529F\u80FD\u51FA\u9519\uFF1A" + st.error) : null,
|
|
4509
7962
|
viewNode
|
|
4510
7963
|
);
|
|
4511
7964
|
})
|
|
4512
7965
|
);
|
|
4513
7966
|
}
|
|
4514
7967
|
function DockChips(props) {
|
|
4515
|
-
const [, force] =
|
|
4516
|
-
|
|
7968
|
+
const [, force] = import_react19.default.useReducer((n) => n + 1, 0);
|
|
7969
|
+
import_react19.default.useEffect(() => subscribeFeatureState(() => force()), []);
|
|
4517
7970
|
const items = [];
|
|
4518
7971
|
for (const f of allModules()) {
|
|
4519
7972
|
if (f.planned || !stateOf(f.id).enabled || !chipShown(f.id) || typeof f.Chip !== "function") continue;
|
|
4520
|
-
items.push(
|
|
7973
|
+
items.push(import_react19.default.createElement(f.Chip, {
|
|
4521
7974
|
key: f.id,
|
|
4522
7975
|
ctx: props.ctx,
|
|
4523
7976
|
feature: f,
|
|
@@ -4527,23 +7980,23 @@ function DockChips(props) {
|
|
|
4527
7980
|
}));
|
|
4528
7981
|
}
|
|
4529
7982
|
if (items.length === 0) return null;
|
|
4530
|
-
return
|
|
7983
|
+
return import_react19.default.createElement("div", { className: "dockchip-row" }, items);
|
|
4531
7984
|
}
|
|
4532
7985
|
function FeatureOverlays() {
|
|
4533
|
-
const [, force] =
|
|
7986
|
+
const [, force] = import_react19.default.useReducer((n) => n + 1, 0);
|
|
4534
7987
|
useExternalVersion();
|
|
4535
|
-
|
|
7988
|
+
import_react19.default.useEffect(() => subscribeFeatureState(() => force()), []);
|
|
4536
7989
|
const items = [];
|
|
4537
7990
|
for (const f of allModules()) {
|
|
4538
7991
|
if (f.planned || !stateOf(f.id).enabled || typeof f.Overlay !== "function") continue;
|
|
4539
|
-
items.push(
|
|
7992
|
+
items.push(import_react19.default.createElement(
|
|
4540
7993
|
FeatureBoundary,
|
|
4541
7994
|
{ key: f.id },
|
|
4542
|
-
|
|
7995
|
+
import_react19.default.createElement(f.Overlay, { ctx: ctxRef.current, feature: f })
|
|
4543
7996
|
));
|
|
4544
7997
|
}
|
|
4545
7998
|
if (items.length === 0) return null;
|
|
4546
|
-
return
|
|
7999
|
+
return import_react19.default.createElement(import_react19.default.Fragment, null, items);
|
|
4547
8000
|
}
|
|
4548
8001
|
var ctxRef = { current: null };
|
|
4549
8002
|
function apply(ctx) {
|
|
@@ -4554,23 +8007,23 @@ function apply(ctx) {
|
|
|
4554
8007
|
if (slots === void 0) return;
|
|
4555
8008
|
slots.inject("sidebar.footer.action", () => slots.register(
|
|
4556
8009
|
{ name: "sidebar.footer.action", id: "dsh-dock", order: 1, label: "\u529F\u80FD\u575E" },
|
|
4557
|
-
(props) =>
|
|
8010
|
+
(props) => import_react19.default.createElement(DockEntry, props)
|
|
4558
8011
|
));
|
|
4559
8012
|
slots.inject("shell.overlay", () => slots.register(
|
|
4560
8013
|
{ name: "shell.overlay", id: "dsh-dock-panel", order: 21, label: "\u529F\u80FD\u575E\u9762\u677F" },
|
|
4561
|
-
() =>
|
|
8014
|
+
() => import_react19.default.createElement(DockModal, null)
|
|
4562
8015
|
));
|
|
4563
8016
|
slots.inject("shell.overlay", () => slots.register(
|
|
4564
8017
|
{ name: "shell.overlay", id: "dsh-dock-feature-overlays", order: 22, label: "\u529F\u80FD\u575E\u5168\u5C40\u6D6E\u5C42" },
|
|
4565
|
-
() =>
|
|
8018
|
+
() => import_react19.default.createElement(FeatureOverlays, null)
|
|
4566
8019
|
));
|
|
4567
8020
|
slots.inject("settings.section", () => slots.register(
|
|
4568
8021
|
{ name: "settings.section", id: "dsh-dock", order: 90, label: "\u529F\u80FD\u575E" },
|
|
4569
|
-
() =>
|
|
8022
|
+
() => import_react19.default.createElement(DockPanel, null)
|
|
4570
8023
|
));
|
|
4571
8024
|
slots.inject("conversation.input.left", () => slots.register(
|
|
4572
8025
|
{ name: "conversation.input.left", id: "dsh-dock-chips", order: 10, label: "\u529F\u80FD\u575E" },
|
|
4573
|
-
(zone) =>
|
|
8026
|
+
(zone) => import_react19.default.createElement(DockChips, Object.assign({}, zone, { ctx: ctxRef.current }))
|
|
4574
8027
|
));
|
|
4575
8028
|
}
|
|
4576
8029
|
var inject = ["timer"];
|