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 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 --code ABCD
243
- npx dou-cl --match doudizhu --server 1.2.3.4:8080
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
- 都可以联网:一个人 `--serve` 开服务器,其他人 `--join <地址> --code <房间号>` 进来,
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 --code ABCD
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 在本机 8080 端口开服务器');
32
- console.log(' npx dou-cl --serve --port 9000 指定端口');
33
- console.log(' npx dou-cl --create doudizhu 连服务器并开一个房间');
34
- console.log(' npx dou-cl --join 1.2.3.4:8080 --code ABCD');
35
- console.log(' npx dou-cl --match doudizhu --server 1.2.3.4:8080 快速匹配');
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 '--match': o.mode = 'match'; o.game = next(); break;
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 '--server': o.server = next(); break;
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 s = server.createServer({ port: opts.port });
172
- console.log(colors.bold(' dou-cl 服务器运行中') + colors.dim(' 端口 ' + opts.port));
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 === 'create' || opts.mode === 'join' || opts.mode === 'match') {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dou-cl",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "伪装成开发控制台的命令行牌桌 —— 斗地主 / 跑得快 / 510K,牌局藏在终端里",
5
5
  "keywords": [
6
6
  "doudizhu",
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' ) { printSkinHelp(); return step(); }
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' || c === '5' || c === '6') {
281
- onlineFlow(io, String(Number(c) - 3), opts).catch(function (e) {
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 === '7') { startServerFlow(io, opts.port); return; }
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
- async function onlineFlow(io, choice, opts) {
296
+ /** 联机 · 加入房间:问昵称 + 地址,地址本身就是房间 */
297
+ async function joinFlow(io, opts) {
300
298
  io.clear();
301
299
  io.say('');
302
- const name = await ask(io, ' 你的昵称 > ') || '玩家';
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.say(' 选择玩法: 1 斗地主 2 跑得快 3 510K');
327
- const g = await ask(io, ' > ');
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(), { mode: 'match', game: key, name: name, server: srv }, opts);
306
+ await client.runClient(new ui.IO(), { server: srv, name: name }, opts);
333
307
  }
334
308
 
335
- function startServerFlow(io, port) {
309
+ /** 联机 · 本机开一个房间(服务器自己不占座位,别人用 --join 连进来) */
310
+ async function startServerFlow(io, opts) {
336
311
  io.clear();
337
312
  io.say('');
338
- io.say(' 启动服务器…(Ctrl+C 停止)');
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.createServer({ port: port || 8080 });
341
- io.say(colors.dim(' 本机 IP 可以用 ipconfig 查,别人执行: npx dou-cl --join <你的IP>:' + (port || 8080)));
342
- io.say(colors.dim(' 也可以让别人快速匹配: npx dou-cl --match doudizhu --server <你的IP>:' + (port || 8080)));
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