maid-poker-cli 1.0.0 → 1.0.1

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.
@@ -0,0 +1,467 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const logic_1 = require("../logic");
4
+ // 辅助函数:快速创建牌
5
+ function createCard(rank, suit = '♠') {
6
+ const RANKS = ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2', 'BJ', 'RJ'];
7
+ const value = RANKS.indexOf(rank) + 3;
8
+ return { suit, rank, value };
9
+ }
10
+ // 辅助函数:创建多张同一花色的牌
11
+ function createCards(ranks, suit = '♠') {
12
+ return ranks.map(r => createCard(r, suit));
13
+ }
14
+ // 辅助函数:创建指定数量的相同牌
15
+ function createSameCards(rank, count) {
16
+ const suits = ['♠', '♥', '♣', '♦'];
17
+ return Array.from({ length: count }, (_, i) => createCard(rank, suits[i % 4]));
18
+ }
19
+ // 辅助函数:创建用于四人模式的双副牌中的相同牌
20
+ function createSameCardsDouble(rank, count) {
21
+ const suits = ['♠', '♥', '♣', '♦'];
22
+ const cards = [];
23
+ for (let i = 0; i < count; i++) {
24
+ const deckIndex = Math.floor(i / 4);
25
+ cards.push({
26
+ suit: suits[i % 4],
27
+ rank,
28
+ value: createCard(rank).value,
29
+ deckIndex
30
+ });
31
+ }
32
+ return cards;
33
+ }
34
+ // 辅助函数:创建4张王(天王炸)
35
+ function createQuadJoker() {
36
+ return [
37
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 0 },
38
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 1 },
39
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 0 },
40
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 1 },
41
+ ];
42
+ }
43
+ describe('三人斗地主 (Three Player Mode)', () => {
44
+ const gameMode = 'three_player';
45
+ describe('牌型识别 (analyzeHand)', () => {
46
+ test('单张', () => {
47
+ const cards = createCards(['7']);
48
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
49
+ expect(result.type).toBe('single');
50
+ expect(result.rankValue).toBe(7);
51
+ });
52
+ test('对子', () => {
53
+ const cards = createSameCards('Q', 2);
54
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
55
+ expect(result.type).toBe('pair');
56
+ expect(result.rankValue).toBe(12);
57
+ });
58
+ test('三张', () => {
59
+ const cards = createSameCards('8', 3);
60
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
61
+ expect(result.type).toBe('triple');
62
+ expect(result.rankValue).toBe(8);
63
+ });
64
+ test('三带一', () => {
65
+ const cards = [...createSameCards('8', 3), createCard('6')];
66
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
67
+ expect(result.type).toBe('triple_one');
68
+ expect(result.rankValue).toBe(8);
69
+ });
70
+ test('三带对', () => {
71
+ const cards = [...createSameCards('8', 3), ...createSameCards('6', 2)];
72
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
73
+ expect(result.type).toBe('triple_pair');
74
+ expect(result.rankValue).toBe(8);
75
+ });
76
+ test('顺子 (5张)', () => {
77
+ const cards = createCards(['3', '4', '5', '6', '7']);
78
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
79
+ expect(result.type).toBe('straight');
80
+ expect(result.rankValue).toBe(7);
81
+ expect(result.length).toBe(5);
82
+ });
83
+ test('顺子 (10-A)', () => {
84
+ const cards = createCards(['10', 'J', 'Q', 'K', 'A']);
85
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
86
+ expect(result.type).toBe('straight');
87
+ expect(result.rankValue).toBe(14); // A的值
88
+ });
89
+ test('顺子不能包含2', () => {
90
+ const cards = createCards(['J', 'Q', 'K', 'A', '2']);
91
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
92
+ expect(result.type).toBe('invalid');
93
+ });
94
+ test('连对 (3对)', () => {
95
+ const cards = [
96
+ ...createSameCards('7', 2),
97
+ ...createSameCards('8', 2),
98
+ ...createSameCards('9', 2)
99
+ ];
100
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
101
+ expect(result.type).toBe('consecutive_pairs');
102
+ expect(result.rankValue).toBe(9);
103
+ });
104
+ test('飞机 (不带)', () => {
105
+ const cards = [...createSameCards('3', 3), ...createSameCards('4', 3)];
106
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
107
+ expect(result.type).toBe('airplane');
108
+ });
109
+ test('飞机带单', () => {
110
+ const cards = [
111
+ ...createSameCards('3', 3),
112
+ ...createSameCards('4', 3),
113
+ createCard('7'),
114
+ createCard('9')
115
+ ];
116
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
117
+ expect(result.type).toBe('airplane');
118
+ });
119
+ test('飞机带对', () => {
120
+ const cards = [
121
+ ...createSameCards('3', 3),
122
+ ...createSameCards('4', 3),
123
+ ...createSameCards('7', 2),
124
+ ...createSameCards('9', 2)
125
+ ];
126
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
127
+ expect(result.type).toBe('airplane');
128
+ });
129
+ test('炸弹', () => {
130
+ const cards = createSameCards('6', 4);
131
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
132
+ expect(result.type).toBe('bomb');
133
+ expect(result.rankValue).toBe(6);
134
+ expect(result.bombLevel).toBe(1);
135
+ });
136
+ test('王炸', () => {
137
+ const cards = [createCard('RJ', ''), createCard('BJ', '')];
138
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
139
+ expect(result.type).toBe('rocket');
140
+ expect(result.bombLevel).toBe(5); // 三人模式王炸最大
141
+ });
142
+ });
143
+ describe('牌型比较 (canBeat)', () => {
144
+ test('大单张打小单张', () => {
145
+ const last = createCards(['7']);
146
+ const curr = createCards(['8']);
147
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
148
+ });
149
+ test('小单张不能打大单张', () => {
150
+ const last = createCards(['8']);
151
+ const curr = createCards(['7']);
152
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
153
+ });
154
+ test('大对子打小对子', () => {
155
+ const last = createSameCards('7', 2);
156
+ const curr = createSameCards('K', 2);
157
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
158
+ });
159
+ test('大炸弹打小炸弹', () => {
160
+ const last = createSameCards('5', 4);
161
+ const curr = createSameCards('9', 4);
162
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
163
+ });
164
+ test('小炸弹不能打大炸弹', () => {
165
+ const last = createSameCards('9', 4);
166
+ const curr = createSameCards('5', 4);
167
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
168
+ });
169
+ test('炸弹打非炸弹', () => {
170
+ const last = createCards(['7']);
171
+ const curr = createSameCards('3', 4);
172
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
173
+ });
174
+ test('炸弹打顺子', () => {
175
+ const last = createCards(['3', '4', '5', '6', '7']);
176
+ const curr = createSameCards('3', 4);
177
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
178
+ });
179
+ test('王炸打炸弹', () => {
180
+ const last = createSameCards('A', 4);
181
+ const curr = [createCard('RJ', ''), createCard('BJ', '')];
182
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
183
+ });
184
+ test('王炸打王炸不行(同级别同点数)', () => {
185
+ const last = [createCard('RJ', ''), createCard('BJ', '')];
186
+ const curr = [createCard('RJ', ''), createCard('BJ', '')];
187
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
188
+ });
189
+ test('不同牌型不能比较(除炸弹)', () => {
190
+ const last = createCards(['7']);
191
+ const curr = createSameCards('A', 2);
192
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
193
+ });
194
+ test('长度不同的顺子不能比较', () => {
195
+ const last = createCards(['3', '4', '5', '6', '7']);
196
+ const curr = createCards(['5', '6', '7', '8', '9', '10']);
197
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
198
+ });
199
+ test('大顺子打小顺子', () => {
200
+ const last = createCards(['3', '4', '5', '6', '7']);
201
+ const curr = createCards(['5', '6', '7', '8', '9']);
202
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
203
+ });
204
+ });
205
+ });
206
+ describe('四人斗地主 (Four Player Mode)', () => {
207
+ const gameMode = 'four_player';
208
+ describe('牌型识别 (analyzeHand)', () => {
209
+ test('单张', () => {
210
+ const cards = createCards(['7']);
211
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
212
+ expect(result.type).toBe('single');
213
+ });
214
+ test('对子', () => {
215
+ const cards = createSameCards('Q', 2);
216
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
217
+ expect(result.type).toBe('pair');
218
+ });
219
+ test('四带二 (带2张单牌)', () => {
220
+ const cards = [...createSameCards('9', 4), createCard('6'), createCard('8')];
221
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
222
+ expect(result.type).toBe('four_with_two');
223
+ expect(result.rankValue).toBe(9);
224
+ });
225
+ test('四带两对', () => {
226
+ const cards = [
227
+ ...createSameCards('9', 4),
228
+ ...createSameCards('5', 2),
229
+ ...createSameCards('7', 2)
230
+ ];
231
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
232
+ expect(result.type).toBe('four_with_pairs');
233
+ expect(result.rankValue).toBe(9);
234
+ });
235
+ test('顺子可以包含2 (A2345)', () => {
236
+ const cards = createCards(['A', '2', '3', '4', '5']);
237
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
238
+ expect(result.type).toBe('straight');
239
+ });
240
+ test('顺子 10JQKA', () => {
241
+ const cards = createCards(['10', 'J', 'Q', 'K', 'A']);
242
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
243
+ expect(result.type).toBe('straight');
244
+ });
245
+ test('炸弹 (4张相同)', () => {
246
+ const cards = createSameCards('6', 4);
247
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
248
+ expect(result.type).toBe('bomb');
249
+ expect(result.bombLevel).toBe(1);
250
+ });
251
+ test('王炸 (1大1小) - 四人模式等级为2', () => {
252
+ // 四人模式中,1大1小的王炸等级为2,介于普通炸弹和八炸之间
253
+ const cards = [
254
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 0 },
255
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 0 },
256
+ ];
257
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
258
+ expect(result.type).toBe('rocket');
259
+ expect(result.bombLevel).toBe(2);
260
+ });
261
+ test('天王炸 (4张王 = 2大王+2小王)', () => {
262
+ // 在双副牌中,4张王就是2大王+2小王,这就是天王炸
263
+ const cards = createQuadJoker();
264
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
265
+ expect(result.type).toBe('quad_joker');
266
+ expect(result.bombLevel).toBe(4); // 天王炸最强
267
+ });
268
+ test('八炸 (8张相同)', () => {
269
+ const cards = createSameCardsDouble('8', 8);
270
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
271
+ expect(result.type).toBe('octo_bomb');
272
+ expect(result.rankValue).toBe(8);
273
+ expect(result.bombLevel).toBe(3);
274
+ });
275
+ });
276
+ describe('牌型比较 (canBeat) - 炸弹等级', () => {
277
+ test('普通炸弹打非炸弹', () => {
278
+ const last = createCards(['7']);
279
+ const curr = createSameCards('3', 4);
280
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
281
+ });
282
+ test('大炸弹打小炸弹 (同为普通炸)', () => {
283
+ const last = createSameCards('5', 4);
284
+ const curr = createSameCards('9', 4);
285
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
286
+ });
287
+ test('八炸打普通炸弹', () => {
288
+ const last = createSameCards('A', 4);
289
+ const curr = createSameCardsDouble('3', 8);
290
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
291
+ });
292
+ test('大八炸打小八炸', () => {
293
+ const last = createSameCardsDouble('5', 8);
294
+ const curr = createSameCardsDouble('9', 8);
295
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
296
+ });
297
+ test('小八炸不能打大八炸', () => {
298
+ const last = createSameCardsDouble('9', 8);
299
+ const curr = createSameCardsDouble('5', 8);
300
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
301
+ });
302
+ test('天王炸打八炸', () => {
303
+ const last = createSameCardsDouble('2', 8); // 2的八炸(最大八炸)
304
+ const curr = createQuadJoker();
305
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
306
+ });
307
+ test('天王炸打普通炸弹', () => {
308
+ const last = createSameCards('A', 4);
309
+ const curr = createQuadJoker();
310
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
311
+ });
312
+ test('普通炸弹不能打八炸', () => {
313
+ const last = createSameCardsDouble('3', 8);
314
+ const curr = createSameCards('2', 4);
315
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
316
+ });
317
+ test('八炸不能打天王炸', () => {
318
+ const last = createQuadJoker();
319
+ const curr = createSameCardsDouble('2', 8);
320
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
321
+ });
322
+ test('四带二的比较', () => {
323
+ const last = [...createSameCards('5', 4), createCard('3'), createCard('7')];
324
+ const curr = [...createSameCards('9', 4), createCard('4'), createCard('8')];
325
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
326
+ });
327
+ test('小四带二不能打大四带二', () => {
328
+ const last = [...createSameCards('9', 4), createCard('3'), createCard('7')];
329
+ const curr = [...createSameCards('5', 4), createCard('4'), createCard('8')];
330
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
331
+ });
332
+ test('炸弹可以打四带二', () => {
333
+ const last = [...createSameCards('A', 4), createCard('3'), createCard('7')];
334
+ const curr = createSameCards('3', 4);
335
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
336
+ });
337
+ test('王炸打普通炸弹', () => {
338
+ const last = createSameCards('2', 4); // 2的炸弹(最大普通炸弹)
339
+ const curr = [
340
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 0 },
341
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 0 },
342
+ ];
343
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
344
+ });
345
+ test('普通炸弹不能打王炸', () => {
346
+ const last = [
347
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 0 },
348
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 0 },
349
+ ];
350
+ const curr = createSameCards('2', 4);
351
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
352
+ });
353
+ test('八炸打王炸', () => {
354
+ const last = [
355
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 0 },
356
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 0 },
357
+ ];
358
+ const curr = createSameCardsDouble('3', 8); // 3的八炸(最小八炸)
359
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
360
+ });
361
+ test('王炸不能打八炸', () => {
362
+ const last = createSameCardsDouble('3', 8);
363
+ const curr = [
364
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 0 },
365
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 0 },
366
+ ];
367
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(false);
368
+ });
369
+ test('天王炸打王炸', () => {
370
+ const last = [
371
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 0 },
372
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 0 },
373
+ ];
374
+ const curr = createQuadJoker();
375
+ expect((0, logic_1.canBeat)(last, curr, gameMode)).toBe(true);
376
+ });
377
+ });
378
+ describe('顺子规则 (四人模式特殊)', () => {
379
+ test('普通顺子 34567', () => {
380
+ const cards = createCards(['3', '4', '5', '6', '7']);
381
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
382
+ expect(result.type).toBe('straight');
383
+ });
384
+ test('顺子 JQKA2 (四人可含2)', () => {
385
+ const cards = createCards(['J', 'Q', 'K', 'A', '2']);
386
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
387
+ expect(result.type).toBe('straight');
388
+ });
389
+ test('顺子不能包含王', () => {
390
+ const cards = [...createCards(['J', 'Q', 'K', 'A']), createCard('RJ', '')];
391
+ const result = (0, logic_1.analyzeHand)(cards, gameMode);
392
+ expect(result.type).toBe('invalid');
393
+ });
394
+ });
395
+ });
396
+ describe('边界情况和无效牌型', () => {
397
+ test('空手牌', () => {
398
+ const result = (0, logic_1.analyzeHand)([], 'three_player');
399
+ expect(result.type).toBe('invalid');
400
+ });
401
+ test('无效的牌型组合', () => {
402
+ const cards = createCards(['3', '5', '7']);
403
+ const result = (0, logic_1.analyzeHand)(cards, 'three_player');
404
+ expect(result.type).toBe('invalid');
405
+ });
406
+ test('两张不同的牌不是对子', () => {
407
+ const cards = createCards(['3', '5']);
408
+ const result = (0, logic_1.analyzeHand)(cards, 'three_player');
409
+ expect(result.type).toBe('invalid');
410
+ });
411
+ test('4张不连续的牌不是顺子', () => {
412
+ const cards = createCards(['3', '5', '7', '9']);
413
+ const result = (0, logic_1.analyzeHand)(cards, 'three_player');
414
+ expect(result.type).toBe('invalid');
415
+ });
416
+ });
417
+ describe('炸弹等级验证', () => {
418
+ describe('三人模式炸弹等级', () => {
419
+ test('王炸等级为5(最高)', () => {
420
+ const cards = [createCard('RJ', ''), createCard('BJ', '')];
421
+ const result = (0, logic_1.analyzeHand)(cards, 'three_player');
422
+ expect(result.bombLevel).toBe(5);
423
+ });
424
+ test('普通炸弹等级为1', () => {
425
+ const cards = createSameCards('K', 4);
426
+ const result = (0, logic_1.analyzeHand)(cards, 'three_player');
427
+ expect(result.bombLevel).toBe(1);
428
+ });
429
+ });
430
+ describe('四人模式炸弹等级顺序', () => {
431
+ test('普通炸弹等级 = 1', () => {
432
+ const cards = createSameCards('K', 4);
433
+ const result = (0, logic_1.analyzeHand)(cards, 'four_player');
434
+ expect(result.bombLevel).toBe(1);
435
+ });
436
+ test('王炸等级 = 2', () => {
437
+ const cards = [
438
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 0 },
439
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 0 },
440
+ ];
441
+ const result = (0, logic_1.analyzeHand)(cards, 'four_player');
442
+ expect(result.bombLevel).toBe(2);
443
+ });
444
+ test('八炸等级 = 3', () => {
445
+ const cards = createSameCardsDouble('K', 8);
446
+ const result = (0, logic_1.analyzeHand)(cards, 'four_player');
447
+ expect(result.bombLevel).toBe(3);
448
+ });
449
+ test('天王炸等级 = 4(最高)', () => {
450
+ const cards = createQuadJoker();
451
+ const result = (0, logic_1.analyzeHand)(cards, 'four_player');
452
+ expect(result.bombLevel).toBe(4);
453
+ });
454
+ test('炸弹等级:天王炸 > 八炸 > 王炸 > 普通炸弹', () => {
455
+ const normalBomb = (0, logic_1.analyzeHand)(createSameCards('2', 4), 'four_player');
456
+ const rocket = (0, logic_1.analyzeHand)([
457
+ { suit: '', rank: 'RJ', value: 17, deckIndex: 0 },
458
+ { suit: '', rank: 'BJ', value: 16, deckIndex: 0 },
459
+ ], 'four_player');
460
+ const octoBomb = (0, logic_1.analyzeHand)(createSameCardsDouble('3', 8), 'four_player');
461
+ const quadJoker = (0, logic_1.analyzeHand)(createQuadJoker(), 'four_player');
462
+ expect(normalBomb.bombLevel).toBeLessThan(rocket.bombLevel);
463
+ expect(rocket.bombLevel).toBeLessThan(octoBomb.bombLevel);
464
+ expect(octoBomb.bombLevel).toBeLessThan(quadJoker.bombLevel);
465
+ });
466
+ });
467
+ });