dou-cl 1.0.1 → 1.0.2
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/PUBLISH.md +3 -3
- package/README.md +7 -4
- package/bin/dou-cl.js +50 -30
- package/package.json +1 -1
- package/src/app.js +30 -43
- package/src/net/client.js +268 -130
- package/src/net/server.js +278 -309
- package/src/ui.js +3 -3
package/PUBLISH.md
CHANGED
|
@@ -238,9 +238,9 @@ npx dou-cl
|
|
|
238
238
|
npx dou-cl -g doudizhu # 跳过菜单,直接开一局(最隐蔽)
|
|
239
239
|
npx dou-cl -g fivek # 510K
|
|
240
240
|
npx dou-cl --skin hex # 换个伪装皮肤
|
|
241
|
-
npx dou-cl --serve --port 8080
|
|
242
|
-
npx dou-cl --join 192.168.1.10:8080
|
|
243
|
-
npx dou-cl --
|
|
241
|
+
npx dou-cl --serve --port 8080 -g doudizhu # 本机开一个房间(服务器不占座位)
|
|
242
|
+
npx dou-cl --join 192.168.1.10:8080 # 连进这个房间(地址本身就是房间)
|
|
243
|
+
npx dou-cl --serve --ports 10000-12000 # 一次开一片房间,每个端口一个
|
|
244
244
|
```
|
|
245
245
|
|
|
246
246
|
### ⚠️ 刚发完那几分钟,npx 会拉不到
|
package/README.md
CHANGED
|
@@ -90,14 +90,17 @@ npx dou-cl --skins # 列出全部
|
|
|
90
90
|
| **跑得快** | 3 | 一副牌去大小王,持 ♠3 者先出且首手必带 ♠3 |
|
|
91
91
|
| **510K** | 4 | 两副牌,5/10/K 计分;五十K 是通吃牌型 |
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
或 `--match <玩法>` 快速匹配,人数不够由电脑补齐。
|
|
93
|
+
联网只有一条规则:**一个 `ip:port` 就是一个房间**。服务器自己不占座位。
|
|
95
94
|
|
|
96
95
|
```bash
|
|
97
|
-
npx dou-cl --serve --port 8080
|
|
98
|
-
npx dou-cl --join 192.168.1.10:8080
|
|
96
|
+
npx dou-cl --serve --port 8080 -g doudizhu # 本机开一个房间
|
|
97
|
+
npx dou-cl --join 192.168.1.10:8080 # 别人连进来,地址本身就是房间
|
|
98
|
+
npx dou-cl --serve --ports 10000-12000 # 一次开一片,每个端口一个独立房间
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
人满了直接回绝;人不够就等着 —— 随时能退出,也能敲一下让电脑补齐先开局。
|
|
102
|
+
换皮肤、`reveal` 这类纯本地的操作**任何时刻都能用**,不用等你的回合。
|
|
103
|
+
|
|
101
104
|
---
|
|
102
105
|
|
|
103
106
|
## License
|
package/bin/dou-cl.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
5
|
* dou-cl —— 命令行斗地主 / 跑得快 / 510K
|
|
6
|
-
*
|
|
6
|
+
* 单机可玩;联网时「一个 ip:port 就是一个房间」。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const path = require('path');
|
|
@@ -27,12 +27,12 @@ function usage() {
|
|
|
27
27
|
console.log(' npx dou-cl -g paodekuai 跑得快');
|
|
28
28
|
console.log(' npx dou-cl -g fivek 510K');
|
|
29
29
|
console.log('');
|
|
30
|
-
console.log(colors.bold(' 联网'));
|
|
31
|
-
console.log(' npx dou-cl --serve
|
|
32
|
-
console.log(' npx dou-cl --serve --port
|
|
33
|
-
console.log(' npx dou-cl --
|
|
34
|
-
console.log(' npx dou-cl --join
|
|
35
|
-
console.log('
|
|
30
|
+
console.log(colors.bold(' 联网') + colors.dim('(一个 ip:port 就是一个房间)'));
|
|
31
|
+
console.log(' npx dou-cl --serve --port 8080 在 8080 开一个房间(默认斗地主)');
|
|
32
|
+
console.log(' npx dou-cl --serve --port 8080 -g fivek 指定房间玩法');
|
|
33
|
+
console.log(' npx dou-cl --serve --ports 10000-12000 一次开一片房间,每个端口一个');
|
|
34
|
+
console.log(' npx dou-cl --join 192.168.1.10:8080 连进这个房间');
|
|
35
|
+
console.log(colors.dim(' 服务器自己不占座位;人满了直接回绝,人不够就等着(s 让电脑补齐,q 退出)'));
|
|
36
36
|
console.log('');
|
|
37
37
|
console.log(colors.bold(' 摸鱼模式') + colors.dim('(默认开启,牌桌伪装成看起来正常的开发输出)'));
|
|
38
38
|
console.log(' npx dou-cl --skin hex 换皮肤: log(默认) / hex / json / diff');
|
|
@@ -42,6 +42,7 @@ function usage() {
|
|
|
42
42
|
console.log('');
|
|
43
43
|
console.log(colors.bold(' 其他'));
|
|
44
44
|
console.log(' --name <昵称> 联网时显示的名字');
|
|
45
|
+
console.log(' --host <地址> 服务器监听地址(默认 0.0.0.0)');
|
|
45
46
|
console.log(' --speed <毫秒> 单机电脑出牌间隔(默认 700)');
|
|
46
47
|
console.log(' --ascii 花色用 S/H/C/D 字母显示(老终端兼容)');
|
|
47
48
|
console.log(' -h, --help 显示帮助');
|
|
@@ -59,14 +60,36 @@ function usage() {
|
|
|
59
60
|
console.log('');
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
/** "8080" / "8080,8081" / "10000-12000" → [8080] / [8080,8081] / [10000..12000] */
|
|
64
|
+
function parsePorts(s) {
|
|
65
|
+
const out = [];
|
|
66
|
+
String(s || '').split(',').forEach(function (part) {
|
|
67
|
+
const p = part.trim();
|
|
68
|
+
if (!p) return;
|
|
69
|
+
const mm = /^(\d+)\s*-\s*(\d+)$/.exec(p);
|
|
70
|
+
if (mm) {
|
|
71
|
+
const a = Number(mm[1]);
|
|
72
|
+
const b = Number(mm[2]);
|
|
73
|
+
for (let i = Math.min(a, b); i <= Math.max(a, b); i++) {
|
|
74
|
+
if (i > 0 && i < 65536) out.push(i);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
const n = Number(p);
|
|
78
|
+
if (n > 0 && n < 65536) out.push(n);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
62
84
|
function parseArgs(argv) {
|
|
63
85
|
const o = {
|
|
64
86
|
mode: 'menu',
|
|
65
87
|
game: null,
|
|
66
88
|
server: process.env.DOUCL_SERVER || '127.0.0.1:8080',
|
|
67
|
-
code: null,
|
|
68
89
|
name: process.env.DOUCL_NAME || null,
|
|
69
90
|
port: Number(process.env.DOUCL_PORT) || 8080,
|
|
91
|
+
ports: null,
|
|
92
|
+
host: process.env.DOUCL_HOST || null,
|
|
70
93
|
speed: 700,
|
|
71
94
|
ascii: false,
|
|
72
95
|
skin: process.env.DOUCL_SKIN || require('../src/skins').DEFAULT_SKIN,
|
|
@@ -85,14 +108,13 @@ function parseArgs(argv) {
|
|
|
85
108
|
case '-v': case '--version': o.version = true; break;
|
|
86
109
|
case '--serve': case '--server-mode': o.mode = 'serve'; break;
|
|
87
110
|
case '--join': o.mode = 'join'; o.server = next(); break;
|
|
88
|
-
case '--
|
|
89
|
-
case '--create': o.mode = 'create'; o.game = next(); break;
|
|
111
|
+
case '--server': o.mode = 'join'; o.server = next(); break;
|
|
90
112
|
case '--local': o.mode = 'local'; o.game = next(); break;
|
|
91
|
-
case '-g': case '--game': o.mode = 'local'; o.game = next(); break;
|
|
92
|
-
case '--
|
|
93
|
-
case '--code': o.code = (next() || '').toUpperCase(); break;
|
|
94
|
-
case '--name': o.name = next(); break;
|
|
113
|
+
case '-g': case '--game': o.mode = o.mode === 'serve' ? 'serve' : 'local'; o.game = next(); break;
|
|
114
|
+
case '--ports': case '--port-range': o.ports = parsePorts(next()); break;
|
|
95
115
|
case '--port': o.port = Number(next()) || 8080; break;
|
|
116
|
+
case '--host': o.host = next(); break;
|
|
117
|
+
case '--name': o.name = next(); break;
|
|
96
118
|
case '--speed': o.speed = Number(next()) || 700; break;
|
|
97
119
|
case '--ascii': o.ascii = true; break;
|
|
98
120
|
case '--no-color': process.env.NO_COLOR = '1'; break;
|
|
@@ -118,6 +140,13 @@ function parseArgs(argv) {
|
|
|
118
140
|
case '-s':
|
|
119
141
|
o.skin = 'log';
|
|
120
142
|
break;
|
|
143
|
+
// 旧协议里的房间号 / 匹配 / 开房已经取消:地址本身就是房间
|
|
144
|
+
case '--code': case '--match': case '--create': {
|
|
145
|
+
console.error(colors.yellow('提示:' + a + ' 已经不需要了 —— 现在一个地址就是一个房间。'));
|
|
146
|
+
console.error(colors.dim(' 直接: npx dou-cl --serve --port 8080 然后别人 npx dou-cl --join <IP>:8080'));
|
|
147
|
+
if ((a === '--match' || a === '--create') && args[i + 1] && args[i + 1][0] !== '-') i++;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
121
150
|
default:
|
|
122
151
|
if (a && a[0] !== '-') {
|
|
123
152
|
if (a === 'exercise' || a === 'drill') { o.mode = 'exercise'; o.exercise = 'menu'; }
|
|
@@ -168,32 +197,23 @@ async function main() {
|
|
|
168
197
|
|
|
169
198
|
if (opts.mode === 'serve') {
|
|
170
199
|
const server = require('../src/net/server');
|
|
171
|
-
const
|
|
172
|
-
|
|
200
|
+
const game = opts.game || 'doudizhu';
|
|
201
|
+
const ports = (opts.ports && opts.ports.length) ? opts.ports : [opts.port];
|
|
202
|
+
const s = server.createServers({ game: game, ports: ports, host: opts.host || '0.0.0.0' });
|
|
203
|
+
console.log(colors.dim(' 服务端不占座位 —— 玩家执行 npx dou-cl --join <本机IP>:<端口> 进来'));
|
|
173
204
|
console.log(colors.dim(' Ctrl+C 停止'));
|
|
205
|
+
void s;
|
|
174
206
|
return;
|
|
175
207
|
}
|
|
176
208
|
|
|
177
|
-
if (opts.mode === '
|
|
209
|
+
if (opts.mode === 'join') {
|
|
178
210
|
const client = require('../src/net/client');
|
|
179
211
|
const io = new ui.IO();
|
|
180
|
-
if (opts.mode === 'join' && !opts.code) {
|
|
181
|
-
const code = await app.ask(io, ' 房间号(4 位)> ');
|
|
182
|
-
opts.code = code.toUpperCase();
|
|
183
|
-
}
|
|
184
|
-
if (opts.mode === 'create' && !opts.game) opts.game = 'doudizhu';
|
|
185
|
-
if (opts.mode === 'match' && !opts.game) opts.game = 'doudizhu';
|
|
186
212
|
if (!opts.name) {
|
|
187
213
|
const n = await app.ask(io, ' 你的昵称 > ');
|
|
188
214
|
opts.name = n || '玩家';
|
|
189
215
|
}
|
|
190
|
-
await client.runClient(io, {
|
|
191
|
-
mode: opts.mode,
|
|
192
|
-
game: opts.game,
|
|
193
|
-
code: opts.code,
|
|
194
|
-
name: opts.name,
|
|
195
|
-
server: opts.server,
|
|
196
|
-
}, opts);
|
|
216
|
+
await client.runClient(io, { server: opts.server, name: opts.name }, opts);
|
|
197
217
|
return;
|
|
198
218
|
}
|
|
199
219
|
|
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -127,7 +127,7 @@ function askHuman(io, engine, state, seat, key) {
|
|
|
127
127
|
if (skin) printStealthHelp(); else printHelp(key);
|
|
128
128
|
return step();
|
|
129
129
|
}
|
|
130
|
-
if (low === 'skins' || low === 'skin'
|
|
130
|
+
if (low === 'skins' || low === 'skin') { printSkinHelp(); return step(); }
|
|
131
131
|
if (low === 'quit' || low === 'exit') {
|
|
132
132
|
if (!skin) io.say(colors.dim('已退出本局。'));
|
|
133
133
|
io.close();
|
|
@@ -258,11 +258,8 @@ function showMenu(io, opts) {
|
|
|
258
258
|
io.say(' ' + colors.bold(m[0]) + ' ' + m[2] + ' ' + colors.dim(m[3]));
|
|
259
259
|
});
|
|
260
260
|
io.say('');
|
|
261
|
-
io.say(' ' + colors.bold('联机'));
|
|
262
|
-
io.say(' ' + colors.bold('4') + '
|
|
263
|
-
colors.bold('5') + ' 加入房间');
|
|
264
|
-
io.say(' ' + colors.bold('6') + ' 快速匹配 ' +
|
|
265
|
-
colors.bold('7') + ' 本机开服务器');
|
|
261
|
+
io.say(' ' + colors.bold('联机') + colors.dim('(一个 ip:port 就是一个房间)'));
|
|
262
|
+
io.say(' ' + colors.bold('4') + ' 加入房间 ' + colors.bold('5') + ' 开一个房间');
|
|
266
263
|
io.say('');
|
|
267
264
|
io.say(' ' + colors.bold('0') + ' 退出');
|
|
268
265
|
io.say(' ' + '\u2500'.repeat(58));
|
|
@@ -277,14 +274,14 @@ function showMenu(io, opts) {
|
|
|
277
274
|
.catch(function (e) { io.say(colors.red('出错: ' + e.message)); showMenu(io, opts); });
|
|
278
275
|
return;
|
|
279
276
|
}
|
|
280
|
-
if (c === '4'
|
|
281
|
-
|
|
277
|
+
if (c === '4') {
|
|
278
|
+
joinFlow(io, opts).catch(function (e) {
|
|
282
279
|
io.say(colors.red('出错: ' + e.message));
|
|
283
280
|
showMenu(io, opts);
|
|
284
281
|
});
|
|
285
282
|
return;
|
|
286
283
|
}
|
|
287
|
-
if (c === '
|
|
284
|
+
if (c === '5') { startServerFlow(io, opts); return; }
|
|
288
285
|
io.say(colors.red('没有这个选项:' + c));
|
|
289
286
|
setTimeout(function () { showMenu(io, opts); }, 600);
|
|
290
287
|
});
|
|
@@ -296,50 +293,40 @@ function ask(io, prompt) {
|
|
|
296
293
|
});
|
|
297
294
|
}
|
|
298
295
|
|
|
299
|
-
|
|
296
|
+
/** 联机 · 加入房间:问昵称 + 地址,地址本身就是房间 */
|
|
297
|
+
async function joinFlow(io, opts) {
|
|
300
298
|
io.clear();
|
|
301
299
|
io.say('');
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (choice === '1') {
|
|
305
|
-
io.say('');
|
|
306
|
-
io.say(' 选择玩法: 1 斗地主 2 跑得快 3 510K');
|
|
307
|
-
const g = await ask(io, ' > ');
|
|
308
|
-
const key = ({ '1': 'doudizhu', '2': 'paodekuai', '3': 'fivek' })[g.trim()] || 'doudizhu';
|
|
309
|
-
const srv = await ask(io, ' 服务器地址(直接回车 = 本机 ' + opts.server + ') > ') || opts.server;
|
|
310
|
-
io.close();
|
|
311
|
-
const client = require('./net/client');
|
|
312
|
-
await client.runClient(new ui.IO(), { mode: 'create', game: key, name: name, server: srv }, opts);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (choice === '2') {
|
|
317
|
-
const code = await ask(io, ' 房间号(4 位) > ');
|
|
318
|
-
const srv = await ask(io, ' 服务器地址(直接回车 = 本机 ' + opts.server + ') > ') || opts.server;
|
|
319
|
-
io.close();
|
|
320
|
-
const client = require('./net/client');
|
|
321
|
-
await client.runClient(new ui.IO(), { mode: 'join', code: code.toUpperCase(), name: name, server: srv }, opts);
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
|
|
300
|
+
io.say(' ' + colors.bold('加入房间') + colors.dim(' 填对方的 ip:port 就行'));
|
|
325
301
|
io.say('');
|
|
326
|
-
io
|
|
327
|
-
const
|
|
328
|
-
const key = ({ '1': 'doudizhu', '2': 'paodekuai', '3': 'fivek' })[g.trim()] || 'doudizhu';
|
|
329
|
-
const srv = await ask(io, ' 服务器地址(直接回车 = 本机 ' + opts.server + ') > ') || opts.server;
|
|
302
|
+
const name = (await ask(io, ' 你的昵称 > ')) || '玩家';
|
|
303
|
+
const srv = (await ask(io, ' 房间地址 ip:port(回车 = ' + opts.server + ') > ')) || opts.server;
|
|
330
304
|
io.close();
|
|
331
305
|
const client = require('./net/client');
|
|
332
|
-
await client.runClient(new ui.IO(), {
|
|
306
|
+
await client.runClient(new ui.IO(), { server: srv, name: name }, opts);
|
|
333
307
|
}
|
|
334
308
|
|
|
335
|
-
|
|
309
|
+
/** 联机 · 本机开一个房间(服务器自己不占座位,别人用 --join 连进来) */
|
|
310
|
+
async function startServerFlow(io, opts) {
|
|
336
311
|
io.clear();
|
|
337
312
|
io.say('');
|
|
338
|
-
io.say('
|
|
313
|
+
io.say(' ' + colors.bold('开一个房间') + colors.dim(' 本机起一个房间,别人连进来'));
|
|
314
|
+
io.say('');
|
|
315
|
+
io.say(' 玩法: ' + colors.bold('1') + ' 斗地主 ' + colors.bold('2') + ' 跑得快 ' + colors.bold('3') + ' 510K');
|
|
316
|
+
const g = await ask(io, ' > ');
|
|
317
|
+
const key = ({ '1': 'doudizhu', '2': 'paodekuai', '3': 'fivek' })[g.trim()] || 'doudizhu';
|
|
318
|
+
const p = await ask(io, ' 端口(回车 = ' + opts.port + ') > ');
|
|
319
|
+
const port = Number(p) || opts.port;
|
|
320
|
+
|
|
321
|
+
const rules = rulesMod.get(key);
|
|
339
322
|
const server = require('./net/server');
|
|
340
|
-
server.
|
|
341
|
-
|
|
342
|
-
io.say(
|
|
323
|
+
server.createServers({ game: key, ports: [port] });
|
|
324
|
+
|
|
325
|
+
io.say('');
|
|
326
|
+
io.say(colors.dim(' 房间玩法:' + rules.name + ' 端口:' + port));
|
|
327
|
+
io.say(colors.dim(' 把地址发给朋友: npx dou-cl --join <你的IP>:' + port));
|
|
328
|
+
io.say(colors.dim(' 本机 IP 用 ipconfig 查;服务端自己不占座位。'));
|
|
329
|
+
io.say(colors.dim(' Ctrl+C 停止。'));
|
|
343
330
|
io.handler = null;
|
|
344
331
|
}
|
|
345
332
|
|