dsh-dock 0.9.5 → 0.9.7
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/LICENSE +18 -18
- package/README.md +325 -325
- package/client.js +42 -8
- package/cordis.patch.yml +2 -2
- package/features/animation/host.js +660 -660
- package/features/animation/view.jsx +2247 -2247
- package/features/balance/host.js +494 -494
- package/features/balance/view.js +351 -351
- package/features/games/games/breakout.jsx +227 -227
- package/features/games/games/gomoku.jsx +244 -244
- package/features/games/games/racer.jsx +169 -169
- package/features/games/games/shared.jsx +118 -118
- package/features/games/games/snake.jsx +144 -144
- package/features/games/games/sokoban.jsx +180 -180
- package/features/games/games/tank.jsx +128 -128
- package/features/games/games/tetris.jsx +308 -308
- package/features/games/games/xiangqi.jsx +277 -277
- package/features/games/tank-core.js +152 -152
- package/features/games/view.jsx +639 -639
- package/features/games/xiangqi-core.js +299 -299
- package/features/heartbeat/view.js +35 -35
- package/features/mobile-relay/gateway.js +472 -415
- package/features/mobile-relay/host.js +570 -546
- package/features/mobile-relay/view.jsx +300 -300
- package/features/modelconfig/host.js +403 -403
- package/features/modelconfig/view.js +490 -490
- package/features/theme/view.js +61 -61
- package/features/tokenlog/host.js +678 -678
- package/features/tokenlog/view.jsx +561 -561
- package/features/visionproxy/host.js +315 -315
- package/index.js +175 -118
- package/package.json +59 -59
- package/src/client.jsx +668 -668
- package/src/host-core.js +98 -95
- package/src/shared.js +167 -130
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
/* 趣味游戏 · 坦克大战核心逻辑(纯函数,无 React)——供组件复用,便于单测。 */
|
|
2
|
-
export const GRID = 15;
|
|
3
|
-
export const EMPTY = 0, STEEL = 1, BRICK = 2, BASE = 3;
|
|
4
|
-
const DIRS = { up: [-1, 0], down: [1, 0], left: [0, -1], right: [0, 1] };
|
|
5
|
-
export const DIR_KEYS = Object.keys(DIRS);
|
|
6
|
-
const SPAWNS = [{ r: 0, c: 0 }, { r: 0, c: 7 }, { r: 0, c: 14 }];
|
|
7
|
-
|
|
8
|
-
function cloneGrid(g) { return g.map((row) => row.slice()); }
|
|
9
|
-
function inBounds(r, c) { return r >= 0 && r < GRID && c >= 0 && c < GRID; }
|
|
10
|
-
function cellBlockedForTank(grid, r, c) { return !inBounds(r, c) || grid[r][c] !== EMPTY; }
|
|
11
|
-
|
|
12
|
-
export function makeGrid() {
|
|
13
|
-
const g = Array.from({ length: GRID }, () => Array(GRID).fill(EMPTY));
|
|
14
|
-
const set = (r, c, v) => { if (r >= 0 && r < GRID && c >= 0 && c < GRID) g[r][c] = v; };
|
|
15
|
-
for (let r = 2; r <= 5; r += 1) { set(r, 3, STEEL); set(r, 11, STEEL); set(r, 7, STEEL); }
|
|
16
|
-
for (let c = 4; c <= 10; c += 1) set(8, c, BRICK);
|
|
17
|
-
set(9, 5, BRICK); set(9, 6, BRICK); set(9, 8, BRICK); set(9, 9, BRICK); set(9, 10, BRICK);
|
|
18
|
-
set(13, 7, BASE);
|
|
19
|
-
set(12, 6, BRICK); set(12, 7, BRICK); set(12, 8, BRICK);
|
|
20
|
-
set(13, 6, BRICK); set(13, 8, BRICK);
|
|
21
|
-
return g;
|
|
22
|
-
}
|
|
23
|
-
export function findBase(grid) {
|
|
24
|
-
for (let r = 0; r < GRID; r += 1) for (let c = 0; c < GRID; c += 1) if (grid[r][c] === BASE) return { r, c };
|
|
25
|
-
return { r: 13, c: 7 };
|
|
26
|
-
}
|
|
27
|
-
function playerSpawn() { return { r: 11, c: 7, dir: "up", alive: true, lives: 3, cooldown: 0 }; }
|
|
28
|
-
|
|
29
|
-
export function initialState() {
|
|
30
|
-
return {
|
|
31
|
-
grid: makeGrid(),
|
|
32
|
-
player: playerSpawn(),
|
|
33
|
-
enemies: [],
|
|
34
|
-
bullets: [],
|
|
35
|
-
score: 0,
|
|
36
|
-
wave: 1,
|
|
37
|
-
remaining: 4,
|
|
38
|
-
active: 2,
|
|
39
|
-
spawnTick: 0,
|
|
40
|
-
over: false, win: false,
|
|
41
|
-
respawnTicks: 0,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function spawnBullet(r, c, dir, from) {
|
|
45
|
-
const [dr, dc] = DIRS[dir];
|
|
46
|
-
return { r: r + dr, c: c + dc, dr, dc, from };
|
|
47
|
-
}
|
|
48
|
-
function spawnOne(grid, enemies) {
|
|
49
|
-
for (const s of SPAWNS) {
|
|
50
|
-
if (grid[s.r][s.c] === EMPTY && !enemies.some((e) => e.r === s.r && e.c === s.c)) {
|
|
51
|
-
return { r: s.r, c: s.c, dir: "down", hp: 1, cooldown: 3, moveTick: 0 };
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function step(state, input) {
|
|
58
|
-
if (state.over || state.win) return state;
|
|
59
|
-
const grid = cloneGrid(state.grid);
|
|
60
|
-
let player = Object.assign({}, state.player);
|
|
61
|
-
let enemies = state.enemies.map((e) => Object.assign({}, e));
|
|
62
|
-
let bullets = state.bullets.map((b) => Object.assign({}, b));
|
|
63
|
-
let remaining = state.remaining, score = state.score, over = state.over;
|
|
64
|
-
let respawnTicks = state.respawnTicks;
|
|
65
|
-
let spawnTick = state.spawnTick + 1;
|
|
66
|
-
|
|
67
|
-
// 玩家移动
|
|
68
|
-
if (player.alive && input.dir && DIRS[input.dir]) {
|
|
69
|
-
const [dr, dc] = DIRS[input.dir];
|
|
70
|
-
player.dir = input.dir;
|
|
71
|
-
const nr = player.r + dr, nc = player.c + dc;
|
|
72
|
-
if (!cellBlockedForTank(grid, nr, nc)) { player.r = nr; player.c = nc; }
|
|
73
|
-
}
|
|
74
|
-
// 玩家射击
|
|
75
|
-
if (player.alive && input.shoot && player.cooldown <= 0) {
|
|
76
|
-
bullets.push(spawnBullet(player.r, player.c, player.dir, "p"));
|
|
77
|
-
player.cooldown = 9;
|
|
78
|
-
}
|
|
79
|
-
player.cooldown = Math.max(0, player.cooldown - 1);
|
|
80
|
-
|
|
81
|
-
// 敌人移动/射击
|
|
82
|
-
for (let i = 0; i < enemies.length; i += 1) {
|
|
83
|
-
const e = enemies[i];
|
|
84
|
-
e.cooldown -= 1; e.moveTick += 1;
|
|
85
|
-
if (e.moveTick % 12 === 0) {
|
|
86
|
-
const [dr, dc] = DIRS[e.dir];
|
|
87
|
-
const nr = e.r + dr, nc = e.c + dc;
|
|
88
|
-
if (cellBlockedForTank(grid, nr, nc) || enemies.some((o, oi) => oi !== i && o.r === nr && o.c === nc)) {
|
|
89
|
-
const opts = DIR_KEYS.filter((d) => {
|
|
90
|
-
const [ddr, ddc] = DIRS[d];
|
|
91
|
-
const tr = e.r + ddr, tc = e.c + ddc;
|
|
92
|
-
return !cellBlockedForTank(grid, tr, tc) && !enemies.some((o, oi) => oi !== i && o.r === tr && o.c === tc);
|
|
93
|
-
});
|
|
94
|
-
if (opts.length) e.dir = opts[Math.floor(Math.random() * opts.length)];
|
|
95
|
-
} else { e.r = nr; e.c = nc; }
|
|
96
|
-
}
|
|
97
|
-
if (e.cooldown <= 0) {
|
|
98
|
-
bullets.push(spawnBullet(e.r, e.c, e.dir, "e"));
|
|
99
|
-
e.cooldown = 9 + Math.floor(Math.random() * 9);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 子弹移动/碰撞
|
|
104
|
-
let baseHit = false, playerHit = false;
|
|
105
|
-
const nextBullets = [];
|
|
106
|
-
const base = findBase(grid);
|
|
107
|
-
for (const b of bullets) {
|
|
108
|
-
const nr = b.r + b.dr, nc = b.c + b.dc;
|
|
109
|
-
if (!inBounds(nr, nc)) continue;
|
|
110
|
-
const cell = grid[nr][nc];
|
|
111
|
-
if (cell === STEEL) continue;
|
|
112
|
-
if (cell === BRICK) { grid[nr][nc] = EMPTY; continue; }
|
|
113
|
-
if (cell === BASE) { if (b.from === "e") baseHit = true; continue; }
|
|
114
|
-
if (b.from === "p") {
|
|
115
|
-
const idx = enemies.findIndex((e) => e.r === nr && e.c === nc);
|
|
116
|
-
if (idx >= 0) { enemies.splice(idx, 1); score += 100; continue; }
|
|
117
|
-
} else if (player.alive && player.r === nr && player.c === nc) {
|
|
118
|
-
player.lives -= 1;
|
|
119
|
-
if (player.lives <= 0) over = true;
|
|
120
|
-
else { player.alive = false; respawnTicks = 24; }
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
b.r = nr; b.c = nc;
|
|
124
|
-
nextBullets.push(b);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 生成敌人
|
|
128
|
-
if (remaining > 0 && enemies.length < state.active && spawnTick % 9 === 0) {
|
|
129
|
-
const fresh = spawnOne(grid, enemies);
|
|
130
|
-
if (fresh) { enemies.push(fresh); remaining -= 1; }
|
|
131
|
-
}
|
|
132
|
-
// 玩家重生
|
|
133
|
-
if (!player.alive && !over) {
|
|
134
|
-
if (respawnTicks <= 0) { player.alive = true; player.r = 11; player.c = 7; player.dir = "up"; player.cooldown = 0; }
|
|
135
|
-
else respawnTicks -= 1;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const win = remaining <= 0 && enemies.length === 0;
|
|
139
|
-
return Object.assign({}, state, {
|
|
140
|
-
grid, player, enemies, bullets: nextBullets, score, remaining,
|
|
141
|
-
over: over || baseHit, win, spawnTick, respawnTicks,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function nextWave(state) {
|
|
146
|
-
return Object.assign({}, state, {
|
|
147
|
-
wave: state.wave + 1,
|
|
148
|
-
remaining: Math.min(3 + state.wave, 8),
|
|
149
|
-
active: Math.min(2 + Math.floor(state.wave / 2), 4),
|
|
150
|
-
enemies: [], win: false, spawnTick: 0,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
1
|
+
/* 趣味游戏 · 坦克大战核心逻辑(纯函数,无 React)——供组件复用,便于单测。 */
|
|
2
|
+
export const GRID = 15;
|
|
3
|
+
export const EMPTY = 0, STEEL = 1, BRICK = 2, BASE = 3;
|
|
4
|
+
const DIRS = { up: [-1, 0], down: [1, 0], left: [0, -1], right: [0, 1] };
|
|
5
|
+
export const DIR_KEYS = Object.keys(DIRS);
|
|
6
|
+
const SPAWNS = [{ r: 0, c: 0 }, { r: 0, c: 7 }, { r: 0, c: 14 }];
|
|
7
|
+
|
|
8
|
+
function cloneGrid(g) { return g.map((row) => row.slice()); }
|
|
9
|
+
function inBounds(r, c) { return r >= 0 && r < GRID && c >= 0 && c < GRID; }
|
|
10
|
+
function cellBlockedForTank(grid, r, c) { return !inBounds(r, c) || grid[r][c] !== EMPTY; }
|
|
11
|
+
|
|
12
|
+
export function makeGrid() {
|
|
13
|
+
const g = Array.from({ length: GRID }, () => Array(GRID).fill(EMPTY));
|
|
14
|
+
const set = (r, c, v) => { if (r >= 0 && r < GRID && c >= 0 && c < GRID) g[r][c] = v; };
|
|
15
|
+
for (let r = 2; r <= 5; r += 1) { set(r, 3, STEEL); set(r, 11, STEEL); set(r, 7, STEEL); }
|
|
16
|
+
for (let c = 4; c <= 10; c += 1) set(8, c, BRICK);
|
|
17
|
+
set(9, 5, BRICK); set(9, 6, BRICK); set(9, 8, BRICK); set(9, 9, BRICK); set(9, 10, BRICK);
|
|
18
|
+
set(13, 7, BASE);
|
|
19
|
+
set(12, 6, BRICK); set(12, 7, BRICK); set(12, 8, BRICK);
|
|
20
|
+
set(13, 6, BRICK); set(13, 8, BRICK);
|
|
21
|
+
return g;
|
|
22
|
+
}
|
|
23
|
+
export function findBase(grid) {
|
|
24
|
+
for (let r = 0; r < GRID; r += 1) for (let c = 0; c < GRID; c += 1) if (grid[r][c] === BASE) return { r, c };
|
|
25
|
+
return { r: 13, c: 7 };
|
|
26
|
+
}
|
|
27
|
+
function playerSpawn() { return { r: 11, c: 7, dir: "up", alive: true, lives: 3, cooldown: 0 }; }
|
|
28
|
+
|
|
29
|
+
export function initialState() {
|
|
30
|
+
return {
|
|
31
|
+
grid: makeGrid(),
|
|
32
|
+
player: playerSpawn(),
|
|
33
|
+
enemies: [],
|
|
34
|
+
bullets: [],
|
|
35
|
+
score: 0,
|
|
36
|
+
wave: 1,
|
|
37
|
+
remaining: 4,
|
|
38
|
+
active: 2,
|
|
39
|
+
spawnTick: 0,
|
|
40
|
+
over: false, win: false,
|
|
41
|
+
respawnTicks: 0,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function spawnBullet(r, c, dir, from) {
|
|
45
|
+
const [dr, dc] = DIRS[dir];
|
|
46
|
+
return { r: r + dr, c: c + dc, dr, dc, from };
|
|
47
|
+
}
|
|
48
|
+
function spawnOne(grid, enemies) {
|
|
49
|
+
for (const s of SPAWNS) {
|
|
50
|
+
if (grid[s.r][s.c] === EMPTY && !enemies.some((e) => e.r === s.r && e.c === s.c)) {
|
|
51
|
+
return { r: s.r, c: s.c, dir: "down", hp: 1, cooldown: 3, moveTick: 0 };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function step(state, input) {
|
|
58
|
+
if (state.over || state.win) return state;
|
|
59
|
+
const grid = cloneGrid(state.grid);
|
|
60
|
+
let player = Object.assign({}, state.player);
|
|
61
|
+
let enemies = state.enemies.map((e) => Object.assign({}, e));
|
|
62
|
+
let bullets = state.bullets.map((b) => Object.assign({}, b));
|
|
63
|
+
let remaining = state.remaining, score = state.score, over = state.over;
|
|
64
|
+
let respawnTicks = state.respawnTicks;
|
|
65
|
+
let spawnTick = state.spawnTick + 1;
|
|
66
|
+
|
|
67
|
+
// 玩家移动
|
|
68
|
+
if (player.alive && input.dir && DIRS[input.dir]) {
|
|
69
|
+
const [dr, dc] = DIRS[input.dir];
|
|
70
|
+
player.dir = input.dir;
|
|
71
|
+
const nr = player.r + dr, nc = player.c + dc;
|
|
72
|
+
if (!cellBlockedForTank(grid, nr, nc)) { player.r = nr; player.c = nc; }
|
|
73
|
+
}
|
|
74
|
+
// 玩家射击
|
|
75
|
+
if (player.alive && input.shoot && player.cooldown <= 0) {
|
|
76
|
+
bullets.push(spawnBullet(player.r, player.c, player.dir, "p"));
|
|
77
|
+
player.cooldown = 9;
|
|
78
|
+
}
|
|
79
|
+
player.cooldown = Math.max(0, player.cooldown - 1);
|
|
80
|
+
|
|
81
|
+
// 敌人移动/射击
|
|
82
|
+
for (let i = 0; i < enemies.length; i += 1) {
|
|
83
|
+
const e = enemies[i];
|
|
84
|
+
e.cooldown -= 1; e.moveTick += 1;
|
|
85
|
+
if (e.moveTick % 12 === 0) {
|
|
86
|
+
const [dr, dc] = DIRS[e.dir];
|
|
87
|
+
const nr = e.r + dr, nc = e.c + dc;
|
|
88
|
+
if (cellBlockedForTank(grid, nr, nc) || enemies.some((o, oi) => oi !== i && o.r === nr && o.c === nc)) {
|
|
89
|
+
const opts = DIR_KEYS.filter((d) => {
|
|
90
|
+
const [ddr, ddc] = DIRS[d];
|
|
91
|
+
const tr = e.r + ddr, tc = e.c + ddc;
|
|
92
|
+
return !cellBlockedForTank(grid, tr, tc) && !enemies.some((o, oi) => oi !== i && o.r === tr && o.c === tc);
|
|
93
|
+
});
|
|
94
|
+
if (opts.length) e.dir = opts[Math.floor(Math.random() * opts.length)];
|
|
95
|
+
} else { e.r = nr; e.c = nc; }
|
|
96
|
+
}
|
|
97
|
+
if (e.cooldown <= 0) {
|
|
98
|
+
bullets.push(spawnBullet(e.r, e.c, e.dir, "e"));
|
|
99
|
+
e.cooldown = 9 + Math.floor(Math.random() * 9);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 子弹移动/碰撞
|
|
104
|
+
let baseHit = false, playerHit = false;
|
|
105
|
+
const nextBullets = [];
|
|
106
|
+
const base = findBase(grid);
|
|
107
|
+
for (const b of bullets) {
|
|
108
|
+
const nr = b.r + b.dr, nc = b.c + b.dc;
|
|
109
|
+
if (!inBounds(nr, nc)) continue;
|
|
110
|
+
const cell = grid[nr][nc];
|
|
111
|
+
if (cell === STEEL) continue;
|
|
112
|
+
if (cell === BRICK) { grid[nr][nc] = EMPTY; continue; }
|
|
113
|
+
if (cell === BASE) { if (b.from === "e") baseHit = true; continue; }
|
|
114
|
+
if (b.from === "p") {
|
|
115
|
+
const idx = enemies.findIndex((e) => e.r === nr && e.c === nc);
|
|
116
|
+
if (idx >= 0) { enemies.splice(idx, 1); score += 100; continue; }
|
|
117
|
+
} else if (player.alive && player.r === nr && player.c === nc) {
|
|
118
|
+
player.lives -= 1;
|
|
119
|
+
if (player.lives <= 0) over = true;
|
|
120
|
+
else { player.alive = false; respawnTicks = 24; }
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
b.r = nr; b.c = nc;
|
|
124
|
+
nextBullets.push(b);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 生成敌人
|
|
128
|
+
if (remaining > 0 && enemies.length < state.active && spawnTick % 9 === 0) {
|
|
129
|
+
const fresh = spawnOne(grid, enemies);
|
|
130
|
+
if (fresh) { enemies.push(fresh); remaining -= 1; }
|
|
131
|
+
}
|
|
132
|
+
// 玩家重生
|
|
133
|
+
if (!player.alive && !over) {
|
|
134
|
+
if (respawnTicks <= 0) { player.alive = true; player.r = 11; player.c = 7; player.dir = "up"; player.cooldown = 0; }
|
|
135
|
+
else respawnTicks -= 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const win = remaining <= 0 && enemies.length === 0;
|
|
139
|
+
return Object.assign({}, state, {
|
|
140
|
+
grid, player, enemies, bullets: nextBullets, score, remaining,
|
|
141
|
+
over: over || baseHit, win, spawnTick, respawnTicks,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function nextWave(state) {
|
|
146
|
+
return Object.assign({}, state, {
|
|
147
|
+
wave: state.wave + 1,
|
|
148
|
+
remaining: Math.min(3 + state.wave, 8),
|
|
149
|
+
active: Math.min(2 + Math.floor(state.wave / 2), 4),
|
|
150
|
+
enemies: [], win: false, spawnTick: 0,
|
|
151
|
+
});
|
|
152
|
+
}
|