-
Notifications
You must be signed in to change notification settings - Fork 0
/
test.js
2984 lines (2952 loc) · 78 KB
/
test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import {
BLACK, WHITE, SQUARES, Chess,
KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN,
} from './chess.js';
import { assert, reject, expect, test, results } from './tester.js';
// ==== inner depth tests ======================================================
Chess.prototype.perft = function(depth) {
const moves = this.moves({ legal: false });
let nodes = 0;
const color = this.turn;
for (let i = 0, len = moves.length; i < len; i++) {
this.move(moves[i]);
if (!this.check()) {
if (depth - 1 > 0) nodes += this.perft(depth - 1);
else nodes++;
}
this.takeback();
}
return nodes;
};
test('PERFT', () => {
const perfts = [
{
fen:
'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1',
depth: 3,
nodes: 97862,
},
{ fen: '8/PPP4k/8/8/8/8/4Kppp/8 w - - 0 1', depth: 4, nodes: 89363 },
{
fen: '8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1',
depth: 4,
nodes: 43238,
},
{
fen: 'rnbqkbnr/p3pppp/2p5/1pPp4/3P4/8/PP2PPPP/RNBQKBNR w KQkq b6 0 4',
depth: 3,
nodes: 23509,
},
];
for (const p of perfts) {
const chess = new Chess(p.fen);
test(p.fen, () => expect(chess.perft(p.depth)).toEqual(p.nodes));
}
}, true);
delete Chess.perft;
// ==== tests ==================================================================
test('ENDCL', () => {
// endgame classification
const endgames = [
{ fen: '8/8/1pp5/8/P1P5/5k2/8/5K2 w - - 0 2', ec: 'KPPkpp' },
];
for (const { fen, ec } of endgames) {
const chess = new Chess(fen);
console.log(chess.endgame);
test('`' + fen + '`', () => assert(chess.endgame === ec));
}
});
test('SSMGN', () => {
// single square move generation
const positions = [
{
fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
square: 'e2',
verbose: false,
moves: ['e3', 'e4'],
},
{
fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
square: 'e9',
verbose: false,
moves: [],
}, // invalid square
{
fen: 'rnbqk1nr/pppp1ppp/4p3/8/1b1P4/2N5/PPP1PPPP/R1BQKBNR w KQkq - 2 3',
square: 'c3',
verbose: false,
moves: [],
}, // pinned piece
{
fen: '8/k7/8/8/8/8/7p/K7 b - - 0 1',
square: 'h2',
verbose: false,
moves: ['h1=Q+', 'h1=R+', 'h1=B', 'h1=N'],
}, // promotion
{
fen:
'r1bq1rk1/1pp2ppp/p1np1n2/2b1p3/2B1P3/2NP1N2/PPPBQPPP/R3K2R w KQ - 0 8',
square: 'e1',
verbose: false,
moves: ['Kf1', 'Kd1', 'O-O', 'O-O-O'],
}, // castling
{
fen:
'r1bq1rk1/1pp2ppp/p1np1n2/2b1p3/2B1P3/2NP1N2/PPPBQPPP/R3K2R w - - 0 8',
square: 'e1',
verbose: false,
moves: ['Kf1', 'Kd1'],
}, // no castling
{
fen: '8/7K/8/8/1R6/k7/1R1p4/8 b - - 0 1',
square: 'a3',
verbose: false,
moves: [],
}, // trapped king
{
fen: '8/7K/8/8/1R6/k7/1R1p4/8 b - - 0 1',
square: 'd2',
verbose: true,
moves: [
{
color: 'b',
from: 'd2',
to: 'd1',
flags: 'np',
piece: 'p',
promotion: 'q',
san: 'd1=Q',
},
{
color: 'b',
from: 'd2',
to: 'd1',
flags: 'np',
piece: 'p',
promotion: 'r',
san: 'd1=R',
},
{
color: 'b',
from: 'd2',
to: 'd1',
flags: 'np',
piece: 'p',
promotion: 'b',
san: 'd1=B',
},
{
color: 'b',
from: 'd2',
to: 'd1',
flags: 'np',
piece: 'p',
promotion: 'n',
san: 'd1=N',
},
],
}, // verbose
{
fen:
'rnbqk2r/ppp1pp1p/5n1b/3p2pQ/1P2P3/B1N5/P1PP1PPP/R3KBNR b KQkq - 3 5',
square: 'f1',
verbose: true,
moves: [],
}, // issue #30
];
for (const position of positions) {
const chess = new Chess(position.fen);
test('`' + position.fen + '` `' + position.square + '`', () => {
const moves = chess.moves({
square: position.square,
verbose: position.verbose,
});
expect(moves).toEqual(position.moves);
})
}
});
test('CHKMT', () => {
// checkmates and other positions
test('MATES', () => {
const checkmates = [
'8/5r2/4K1q1/4p3/3k4/8/8/8 w - - 0 7',
'4r2r/p6p/1pnN2p1/kQp5/3pPq2/3P4/PPP3PP/R5K1 b - - 0 2',
'r3k2r/ppp2p1p/2n1p1p1/8/2B2P1q/2NPb1n1/PP4PP/R2Q3K w kq - 0 8',
'8/6R1/pp1r3p/6p1/P3R1Pk/1P4P1/7K/8 b - - 0 4',
];
for (const fen of checkmates) {
const chess = new Chess(fen);
test('`' + fen + '`', () => assert(chess.checkmate() && !chess.draw()))
}
});
test('NOMTS', () => {
const noCheckmates = [
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
'1R6/8/8/8/8/8/7R/k6K b - - 0 1', // stalemate
];
for (const fen of noCheckmates) {
const chess = new Chess(fen);
test('`' + fen + '`', () => assert(!chess.checkmate()))
}
});
});
test('STLMT', () => {
// stalemates
const stalemates = [
'1R6/8/8/8/8/8/7R/k6K b - - 0 1',
'8/8/5k2/p4p1p/P4K1P/1r6/8/8 w - - 0 2',
];
for (const fen of stalemates) {
const chess = new Chess(fen);
test('`' + fen + '`', () => assert(chess.stalemate() && chess.draw()))
}
});
test('INSFM', () => {
// insufficient material and not drawn positions
const drawn = [
'8/8/8/8/8/8/8/k6K w - - 0 1',
'8/2N5/8/8/8/8/8/k6K w - - 0 1',
'8/2b5/8/8/8/8/8/k6K w - - 0 1',
'8/b7/3B4/8/8/8/8/k6K w - - 0 1',
'8/b1B1b1B1/1b1B1b1B/8/8/8/8/1k5K w - - 0 1',
];
const notDrawn = [
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
'8/2p5/8/8/8/8/8/k6K w - - 0 1',
'8/b7/B7/8/8/8/8/k6K w - - 0 1',
'8/bB2b1B1/1b1B1b1B/8/8/8/8/1k5K w - - 0 1',
];
for (const fen of drawn) {
const chess = new Chess(fen);
test('`' + fen + '`', () => {
assert(chess.insufficient());
assert(chess.draw());
});
}
for (const fen of notDrawn) {
const chess = new Chess(fen);
test('`' + fen + '`', () => {
reject(chess.insufficient());
reject(chess.draw());
});
}
});
test('3FOLD', () => {
// threefold repetition
const positions = [
{
fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
moves: 'Nf3 Nf6 Ng1 Ng8 Nf3 Nf6 Ng1 Ng8',
},
{
// Fischer - Petrosian, Buenos Aires, 1971
fen: '8/pp3p1k/2p2q1p/3r1P2/5R2/7P/P1P1QP2/7K b - - 2 30',
moves: 'Qe5 Qh5 Qf6 Qe2 Re5 Qd3 Rd5 Qe2',
},
];
for (const { fen, moves } of positions) {
const chess = new Chess(fen);
test('`' + fen + '`', () => {
moves.split(/\s+/).forEach(move => {
reject(chess.threefold());
chess.move(move);
});
assert(chess.threefold());
assert(chess.draw());
});
}
});
test('SANMV', () => {
// tandard algebraic move notation
const positions = [
{
fen: '7k/3R4/3p2Q1/6Q1/2N1N3/8/8/3R3K w - - 0 1',
moves: `Rd8# Re7 Rf7 Rg7 Rh7# R7xd6 Rc7 Rb7 Ra7 Qf7 Qe8# Qg7# Qg8# Qh7#
Q6h6# Q6h5# Q6f5 Q6f6# Qe6 Qxd6 Q5f6# Qe7 Qd8# Q5h6# Q5h5# Qh4# Qg4 Qg3
Qg2 Qg1 Qf4 Qe3 Qd2 Qc1 Q5f5 Qe5+ Qd5 Qc5 Qb5 Qa5 Na5 Nb6 Ncxd6 Ne5 Ne3
Ncd2 Nb2 Na3 Nc5 Nexd6 Nf6 Ng3 Nf2 Ned2 Nc3 Rd2 Rd3 Rd4 Rd5 R1xd6 Re1
Rf1 Rg1 Rc1 Rb1 Ra1 Kg2 Kh2 Kg1`,
},
{
fen: '1r3k2/P1P5/8/8/8/8/8/R3K2R w KQ - 0 1',
moves: `a8=Q a8=R a8=B a8=N axb8=Q+ axb8=R+ axb8=B axb8=N c8=Q+ c8=R+
c8=B c8=N cxb8=Q+ cxb8=R+ cxb8=B cxb8=N Ra2 Ra3 Ra4 Ra5 Ra6 Rb1 Rc1 Rd1
Kd2 Ke2 Kf2 Kf1 Kd1 Rh2 Rh3 Rh4 Rh5 Rh6 Rh7 Rh8+ Rg1 Rf1+ O-O+ O-O-O`,
},
{
fen: '5rk1/8/8/8/8/8/2p5/R3K2R w KQ - 0 1',
moves: `Ra2 Ra3 Ra4 Ra5 Ra6 Ra7 Ra8 Rb1 Rc1 Rd1 Kd2 Ke2 Rh2 Rh3 Rh4 Rh5
Rh6 Rh7 Rh8+ Rg1+ Rf1`,
},
{
fen: '5rk1/8/8/8/8/8/2p5/R3K2R b KQ - 0 1',
moves: `Rf7 Rf6 Rf5 Rf4 Rf3 Rf2 Rf1+ Re8+ Rd8 Rc8 Rb8 Ra8 Kg7 Kf7 c1=Q+
c1=R+ c1=B c1=N`,
},
{
fen:
'r3k2r/p2pqpb1/1n2pnp1/2pPN3/1p2P3/2N2Q1p/PPPB1PPP/R3K2R w KQkq c6 0 2',
moves: `gxh3 Qxf6 Qxh3 Nxd7 Nxf7 Nxg6 dxc6 dxe6 Rg1 Rf1 Ke2 Kf1 Kd1 Rb1
Rc1 Rd1 g3 g4 Be3 Bf4 Bg5 Bh6 Bc1 b3 a3 a4 Qf4 Qf5 Qg4 Qh5 Qg3 Qe2 Qd1
Qe3 Qd3 Na4 Nb5 Ne2 Nd1 Nb1 Nc6 Ng4 Nd3 Nc4 d6 O-O O-O-O`,
},
{
fen: 'k7/8/K7/8/3n3n/5R2/3n4/8 b - - 0 1',
moves: `N2xf3 Nhxf3 Nd4xf3 N2b3 Nc4 Ne4 Nf1 Nb1 Nhf5 Ng6 Ng2 Nb5 Nc6 Ne6
Ndf5 Ne2 Nc2 N4b3 Kb8`,
},
];
for (const { fen, moves } of positions) {
const chess = new Chess(fen);
test('`' + fen + '`', () => {
// use .sort() to ignore the order in which the moves appear in the move list
expect(chess.moves().sort()).toEqual(moves.split(/\s+|\n/).sort());
});
}
});
test('GPRMV', () => {
// get put remove
const chess = new Chess();
let passed = true;
const positions = [
{
// 8/PpNnBbRr/Qq6/8/K6k/8/8/8 w - - 0 1
// should fail, the king is in check
pieces: {
a7: { type: PAWN, color: WHITE },
b7: { type: PAWN, color: BLACK },
c7: { type: KNIGHT, color: WHITE },
d7: { type: KNIGHT, color: BLACK },
e7: { type: BISHOP, color: WHITE },
f7: { type: BISHOP, color: BLACK },
g7: { type: ROOK, color: WHITE },
h7: { type: ROOK, color: BLACK },
a6: { type: QUEEN, color: WHITE },
b6: { type: QUEEN, color: BLACK },
a4: { type: KING, color: WHITE },
h4: { type: KING, color: BLACK },
},
should_pass: false,
},
{
pieces: { a7: { type: 'z', color: WHITE } }, // bad piece
should_pass: false,
},
{
pieces: { j4: { type: PAWN, color: WHITE } }, // bad square
should_pass: false,
},
// disallow two kings (black)
{
pieces: {
a7: { type: KING, color: BLACK },
h2: { type: KING, color: WHITE },
a8: { type: KING, color: BLACK },
},
should_pass: false,
},
// disallow two kings (white)
{
pieces: {
a7: { type: KING, color: BLACK },
h2: { type: KING, color: WHITE },
h1: { type: KING, color: WHITE },
},
should_pass: false,
},
// allow two kings if overwriting the exact same square
// however should't pass since removing the same piece twice
{
pieces: {
a7: { type: KING, color: BLACK },
h2: { type: KING, color: WHITE },
h2: { type: KING, color: WHITE },
},
should_pass: false,
},
];
for (let i = 0; i < positions.length; i++) {
const { pieces, should_pass } = positions[i];
passed = true;
chess.clear();
test(i + ' - ' + should_pass, () => {
// places the pieces
for (const square in pieces)
passed &&= chess.set(pieces[square], square);
// iterate over every square to make sure get
// returns the proper piece values / color
for (let j = 0; j < SQUARES.length; j++) {
const square = SQUARES[j];
if (!(square in pieces)) {
if (chess.get(square)) {
passed = false;
break;
}
} else {
const piece = chess.get(square);
if (!(
piece &&
piece.type == pieces[square].type &&
piece.color == pieces[square].color
)) {
passed = false;
break;
}
}
}
if (passed) {
// remove the pieces
for (let j = 0; j < SQUARES.length; j++) {
const square = SQUARES[j];
const piece = chess.remove(square);
if (!(square in pieces) && piece) {
passed = false;
break;
}
if (piece && (
pieces[square].type != piece.type ||
pieces[square].color != piece.color
)) {
passed = false;
break;
}
}
}
// finally, check for an empty chess
passed = passed && chess.fen() == '8/8/8/8/8/8/8/8 w - - 0 1';
// some tests should fail, so make sure we're
// supposed to pass / fail each test
expect(passed).toBe(should_pass);
});
}
});
test('FENOT', () => {
// Forsyth-Edwards notation
const validPositions = [
'8/8/8/8/8/8/8/8 w - - 0 1',
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1',
'1nbqkbn1/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/1NBQKBN1 b - - 1 2',
];
const invalidPositions = [
//incomplete FEN string
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBN w KQkq - 0 1',
// bad digit (9)
'rnbqkbnr/pppppppp/9/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
// bad piece (X)
'1nbqkbn1/pppp1ppX/8/4p3/4P3/8/PPPP1PPP/1NBQKBN1 b - - 1 2',
// bad ep square
'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e9 0 1',
];
const chess = new Chess();
for (const fen of validPositions) test('`' + fen + '`', () => {
assert(chess.fen(fen));
expect(chess.fen()).toEqual(fen);
});
for (const fen of invalidPositions) test('`' + fen + '`', () => {
reject(chess.fen(fen));
});
});
test('PGNOT', () => {
// portable game notation
const positions = [
{
moves: `d4 d5 Nf3 Nc6 e3 e6 Bb5 g5 O-O Qf6 Nc3 Bd7 Bxc6 Bxc6 Re1 O-O-O a4
Bb4 a5 b5 axb6 axb6 Ra8+ Kd7 Ne5+ Kd6 Rxd8+ Qxd8 Nxf7+ Ke7 Nxd5+ Qxd5
c3 Kxf7 Qf3+ Qxf3 gxf3 Bxf3 cxb4 e5 dxe5 Ke6 b3 Kxe5 Bb2+ Ke4 Bxh8 Nf6
Bxf6 h5 Bxg5 Bg2 Kxg2 Kf5 Bh4 Kg4 Bg3 Kf5 e4+ Kg4 e5 h4 Bxh4 Kxh4 e6 c5
bxc5 bxc5 e7 c4 bxc4 Kg4 e8=Q Kf5 Qe5+ Kg4 Re4#`,
header: {
'White': 'Jeff Hlywa',
'Black': 'Steve Bragg',
'GreatestGameEverPlayed?': 'True'
},
wrap: 19,
endings: '<br />',
pgn:
'[White "Jeff Hlywa"]<br />[Black "Steve Bragg"]<br />[GreatestGameEverPlayed? "True"]<br /><br />1. d4 d5 2. Nf3 Nc6<br />3. e3 e6 4. Bb5 g5<br />5. O-O Qf6<br />6. Nc3 Bd7<br />7. Bxc6 Bxc6<br />8. Re1 O-O-O<br />9. a4 Bb4 10. a5 b5<br />11. axb6 axb6<br />12. Ra8+ Kd7<br />13. Ne5+ Kd6<br />14. Rxd8+ Qxd8<br />15. Nxf7+ Ke7<br />16. Nxd5+ Qxd5<br />17. c3 Kxf7<br />18. Qf3+ Qxf3<br />19. gxf3 Bxf3<br />20. cxb4 e5<br />21. dxe5 Ke6<br />22. b3 Kxe5<br />23. Bb2+ Ke4<br />24. Bxh8 Nf6<br />25. Bxf6 h5<br />26. Bxg5 Bg2<br />27. Kxg2 Kf5<br />28. Bh4 Kg4<br />29. Bg3 Kf5<br />30. e4+ Kg4<br />31. e5 h4<br />32. Bxh4 Kxh4<br />33. e6 c5<br />34. bxc5 bxc5<br />35. e7 c4<br />36. bxc4 Kg4<br />37. e8=Q Kf5<br />38. Qe5+ Kg4<br />39. Re4#',
fen: '8/8/8/4Q3/2P1R1k1/8/5PKP/8 b - - 4 39',
},
{
moves: `c4 e6 Nf3 d5 d4 Nf6 Nc3 Be7 Bg5 O-O e3 h6 Bh4 b6 cxd5 Nxd5 Bxe7
Qxe7 Nxd5 exd5 Rc1 Be6 Qa4 c5 Qa3 Rc8 Bb5 a6 dxc5 bxc5 O-O Ra7 Be2 Nd7
Nd4 Qf8 Nxe6 fxe6 e4 d4 f4 Qe7 e5 Rb8 Bc4 Kh8 Qh3 Nf8 b3 a5 f5 exf5
Rxf5 Nh7 Rcf1 Qd8 Qg3 Re7 h4 Rbb7 e6 Rbc7 Qe5 Qe8 a4 Qd8 R1f2 Qe8 R2f3
Qd8 Bd3 Qe8 Qe4 Nf6 Rxf6 gxf6 Rxf6 Kg8 Bc4 Kh8 Qf4`,
header: {
'Event': 'Reykjavik WCh',
'Site': 'Reykjavik WCh',
'Date': '1972.01.07',
'EventDate': '?',
'Round': '6',
'Result': '1-0',
'White': 'Robert James Fischer',
'Black': 'Boris Spassky',
'ECO': 'D59',
'WhiteElo': '?',
'BlackElo': '?',
'PlyCount': '81'
},
wrap: 65,
pgn:
'[Event "Reykjavik WCh"]\n[Site "Reykjavik WCh"]\n[Date "1972.01.07"]\n[EventDate "?"]\n[Round "6"]\n[Result "1-0"]\n[White "Robert James Fischer"]\n[Black "Boris Spassky"]\n[ECO "D59"]\n[WhiteElo "?"]\n[BlackElo "?"]\n[PlyCount "81"]\n\n1. c4 e6 2. Nf3 d5 3. d4 Nf6 4. Nc3 Be7 5. Bg5 O-O 6. e3 h6\n7. Bh4 b6 8. cxd5 Nxd5 9. Bxe7 Qxe7 10. Nxd5 exd5 11. Rc1 Be6\n12. Qa4 c5 13. Qa3 Rc8 14. Bb5 a6 15. dxc5 bxc5 16. O-O Ra7\n17. Be2 Nd7 18. Nd4 Qf8 19. Nxe6 fxe6 20. e4 d4 21. f4 Qe7\n22. e5 Rb8 23. Bc4 Kh8 24. Qh3 Nf8 25. b3 a5 26. f5 exf5\n27. Rxf5 Nh7 28. Rcf1 Qd8 29. Qg3 Re7 30. h4 Rbb7 31. e6 Rbc7\n32. Qe5 Qe8 33. a4 Qd8 34. R1f2 Qe8 35. R2f3 Qd8 36. Bd3 Qe8\n37. Qe4 Nf6 38. Rxf6 gxf6 39. Rxf6 Kg8 40. Bc4 Kh8 41. Qf4 1-0',
fen: '4q2k/2r1r3/4PR1p/p1p5/P1Bp1Q1P/1P6/6P1/6K1 b - - 4 41',
},
{
moves: `f3 e5 g4 Qh4#`, // testing wrap being small and having no comments
header: {},
wrap: 1,
pgn: '1. f3 e5\n2. g4 Qh4#',
fen: 'rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3',
},
{
moves: `Ba5 O-O d6 d4`, // testing a non-starting position
header: {},
wrap: 20,
pgn:
'[SetUp "1"]\n[FEN "r1bqk1nr/pppp1ppp/2n5/4p3/1bB1P3/2P2N2/P2P1PPP/RNBQK2R b KQkq - 0 1"]\n\n1. ... Ba5 2. O-O d6\n3. d4',
starting_position:
'r1bqk1nr/pppp1ppp/2n5/4p3/1bB1P3/2P2N2/P2P1PPP/RNBQK2R b KQkq - 0 1',
fen: 'r1bqk1nr/ppp2ppp/2np4/b3p3/2BPP3/2P2N2/P4PPP/RNBQ1RK1 b kq d3 0 3',
},
];
for (let i = 0; i < positions.length; i++) {
const position = positions[i];
test(i, () => {
const chess = new Chess();
if (position.starting_position)
chess.fen(position.starting_position);
position.moves.split(/\s+|\n/).forEach(move => {
expect(chess.move(move)).toBeNot(null);
});
chess.header(position.header);
expect(chess.fen()).toBe(position.fen);
expect(chess.pgn({
wrap: position.wrap,
endings: position.endings,
})).toBe(position.pgn);
});
}
});
test('LOPGN', () => {
// load pgn
const chess = new Chess();
const tests = [
{
pgn: [
'[Event "Reykjavik WCh"]',
'[Site "Reykjavik WCh"]',
'[Date "1972.01.07"]',
'[EventDate "?"]',
'[Round "6"]',
'[Result "1-0"]',
'[White "Robert James Fischer"]',
'[Black "Boris Spassky"]',
'[ECO "D59"]',
'[WhiteElo "?"]',
'[BlackElo "?"]',
'[PlyCount "81"]',
'',
'1. c4 e6 2. Nf3 d5 3. d4 Nf6 4. Nc3 Be7 5. Bg5 O-O 6. e3 h6',
'7. Bh4 b6 8. cxd5 Nxd5 9. Bxe7 Qxe7 10. Nxd5 exd5 11. Rc1 Be6',
'12. Qa4 c5 13. Qa3 Rc8 14. Bb5 a6 15. dxc5 bxc5 16. O-O Ra7',
'17. Be2 Nd7 18. Nd4 Qf8 19. Nxe6 fxe6 20. e4 d4 21. f4 Qe7',
'22. e5 Rb8 23. Bc4 Kh8 24. Qh3 Nf8 25. b3 a5 26. f5 exf5',
'27. Rxf5 Nh7 28. Rcf1 Qd8 29. Qg3 Re7 30. h4 Rbb7 31. e6 Rbc7',
'32. Qe5 Qe8 33. a4 Qd8 34. R1f2 Qe8 35. R2f3 Qd8 36. Bd3 Qe8',
'37. Qe4 Nf6 38. Rxf6 gxf6 39. Rxf6 Kg8 40. Bc4 Kh8 41. Qf4 1-0',
],
expect: true,
},
{
fen: '1n1Rkb1r/p4ppp/4q3/4p1B1/4P3/8/PPP2PPP/2K5 b k - 1 17',
pgn: [
'[Event "Paris"]',
'[Site "Paris"]',
'[Date "1858.??.??"]',
'[EventDate "?"]',
'[Round "?"]',
'[Result "1-0"]',
'[White "Paul Morphy"]',
'[Black "Duke Karl / Count Isouard"]',
'[ECO "C41"]',
'[WhiteElo "?"]',
'[BlackElo "?"]',
'[PlyCount "33"]',
'',
'1.e4 e5 2.Nf3 d6 3.d4 Bg4 {This is a weak move',
'already.--Fischer} 4.dxe5 Bxf3 5.Qxf3 dxe5 6.Bc4 Nf6 7.Qb3 Qe7',
'8.Nc3 c6 9.Bg5 {Black is in what\'s like a zugzwang position',
'here. He can\'t develop the [Queen\'s] knight because the pawn',
'is hanging, the bishop is blocked because of the',
'Queen.--Fischer} b5 10.Nxb5 cxb5 11.Bxb5+ Nbd7 12.O-O-O Rd8',
'13.Rxd7 Rxd7 14.Rd1 Qe6 15.Bxd7+ Nxd7 16.Qb8+ Nxb8 17.Rd8# 1-0',
],
expect: true,
},
// load PGN with comment before first move
{
fen: 'r1bqk2r/pp1nbppp/2p1pn2/3p4/2PP4/5NP1/PP2PPBP/RNBQ1RK1 w kq - 4 7',
pgn: [
'[Event "2012 ROCHESTER GRAND WINTER OPEN"]',
'[Site "Rochester"]',
'[Date "2012.02.04"]',
'[Round "1"]',
'[White "Jensen, Matthew"]',
'[Black "Gaustad, Kevin"]',
'[Result "1-0"]',
'[ECO "E01"]',
'[WhiteElo "2131"]',
'[BlackElo "1770"]',
'[Annotator "Jensen, Matthew"]',
'',
'{ Kevin and I go way back. I checked the USCF player stats and my previous',
'record against Kevin was 4 losses and 1 draw out of 5 games. All of our',
'previous games were between 1992-1998. }',
'1.d4 Nf6 2.c4 e6 3.g3 { Avrukh says',
'to play 3.g3 instead of 3.Nf3 in case the Knight later comes to e2, as in the',
'Bogo-Indian. } 3...d5 4.Bg2 c6 5.Nf3 Be7 6.O-O Nbd7',
'1-0',
],
expect: true,
},
{
pgn: [
'1. e4 e5 2. f4 exf4 3. Nf3 g5 4. h4 g4 5. Ne5 Nf6 6. Nxg4 Nxe4',
'7. d3 Ng3 8. Bxf4 Nxh1 9. Qe2+ Qe7 10. Nf6+ Kd8 11. Bxc7+ Kxc7',
'12. Nd5+ Kd8 13. Nxe7 Bxe7 14. Qg4 d6 15. Qf4 Rg8 16. Qxf7 Bxh4+',
'17. Kd2 Re8 18. Na3 Na6 19. Qh5 Bf6 20. Qxh1 Bxb2 21. Qh4+ Kd7',
'22. Rb1 Bxa3 23. Qa4+',
],
expect: true,
},
// regression test - broken PGN parser ended up here:
// fen = rnbqk2r/pp1p1ppp/4pn2/1N6/1bPN4/8/PP2PPPP/R1BQKB1R b KQkq - 2 6
{
pgn: ['1. d4 Nf6 2. c4 e6 3. Nf3 c5 4. Nc3 cxd4 5. Nxd4 Bb4 6. Nb5'],
fen: 'rnbqk2r/pp1p1ppp/4pn2/1N6/1bP5/2N5/PP2PPPP/R1BQKB1R b KQkq - 2 6',
expect: true,
},
{ pgn: ['1. e4 Qxd7 1/2-1/2'], expect: false },
{
pgn: ['1. e4!! e5?! 2. d4?? d5!?'],
fen: 'rnbqkbnr/ppp2ppp/8/3pp3/3PP3/8/PPP2PPP/RNBQKBNR w KQkq d6 0 3',
expect: true,
},
{ pgn: ['1. e4!+'], expect: false },
{
pgn: [
'1.e4 e6 2.d4 d5 3.exd5 c6?? 4.dxe6 Nf6?! 5.exf7+!! Kd7!? 6.Nf3 Bd6 7.f8=N+!! Qxf8',
],
fen: 'rnb2q1r/pp1k2pp/2pb1n2/8/3P4/5N2/PPP2PPP/RNBQKB1R w KQ - 0 8',
expect: true,
},
{
pgn: ['1. e4 ( 1. d4 { Queen\'s pawn } d5 ( 1... Nf6 ) ) e5'],
fen: 'rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2',
expect: true,
},
{
pgn: [
'1. e4 c5 2. Nf3 e6 { Sicilian Defence, French Variation } 3. Nc3 a6',
'4. Be2 Nc6 5. d4 cxd4 6. Nxd4 Qc7 7. O-O Nf6 8. Be3 Be7 9. f4 d6',
'10. Kh1 O-O 11. Qe1 Nxd4 12. Bxd4 b5 13. Qg3 Bb7 14. a3 Rad8',
'15. Rae1 Rd7 16. Bd3 Qd8 17. Qh3 g6? { (0.05 → 1.03) Inaccuracy.',
'The best move was h6. } (17... h6 18. Rd1 Re8 19. Qg3 Nh5 20. Qg4',
'Nf6 21. Qh3 Bc6 22. Kg1 Qb8 23. Qg3 Nh5 24. Qf2 Bf6 25. Be2 Bxd4',
'26. Rxd4 Nf6 27. g3) 18. f5 e5',
],
fen: '3q1rk1/1b1rbp1p/p2p1np1/1p2pP2/3BP3/P1NB3Q/1PP3PP/4RR1K w - - 0 19',
expect: true,
},
{
pgn: [
'1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5 4. b4 Bb6 5. a4 a6 6. c3 Nf6 7. d3 d6',
'8. Nbd2 O-O 9. O-O Ne7 10. d4 Ng6 11. dxe5 Nxe5 12. Nxe5 dxe5 13. Qb3 Ne8',
'14. Nf3 Nd6 15. Rd1 Bg4 16. Be2 Qf6 17. c4 Bxf3 18. Bxf3 Bd4 19. Rb1 b5 $2',
'20. c5 Nc4 21. Rf1 Qg6 22. Qc2 c6 23. Be2 Rfd8 24. a5 h5 $2 (24... Rd7 $11)',
'25. Rb3 $1 h4 26. Rh3 Qf6 27. Rf3',
],
fen: 'r2r2k1/5pp1/p1p2q2/PpP1p3/1PnbP2p/5R2/2Q1BPPP/2B2RK1 b - - 3 27',
expect: true,
},
{
pgn: [
'1. d4 d5 2. Bf4 Nf6 3. e3 g6 4. Nf3 (4. Nc3 Bg7 5. Nf3 O-O 6. Be2 c5)',
'4... Bg7 5. h3 { 5. Be2 O-O 6. O-O c5 7. c3 Nc6 } 5... O-O',
],
fen: 'rnbq1rk1/ppp1ppbp/5np1/3p4/3P1B2/4PN1P/PPP2PP1/RN1QKB1R w KQ - 1 6',
expect: true,
},
// test the sloppy PGN parser
{
pgn: [
'1.e4 e5 2.Nf3 d6 3.d4 Bg4 4.dxe5 Bxf3 5.Qxf3 dxe5 6.Qf5 Nc6 7.Bb5 Nge7',
'8.Qxe5 Qd7 9.O-O Nxe5 10.Bxd7+ Nxd7 11.Rd1 O-O-O 12.Nc3 Ng6 13.Be3 a6',
'14.Ba7 b6 15.Na4 Kb7 16.Bxb6 cxb6 17.b3 b5 18.Nb2 Nge5 19.f3 Rc8',
'20.Rac1 Ba3 21.Rb1 Rxc2 22.f4 Ng4 23.Rxd7+ Kc6 24.Rxf7 Bxb2 25.Rxg7',
'Ne3 26.Rg3 Bd4 27.Kh1 Rxa2 28.Rc1+ Kb6 29.e5 Rf8 30.e6 Rxf4 31.e7 Re4',
'32.Rg7 Bxg7',
],
fen: '8/4P1bp/pk6/1p6/4r3/1P2n3/r5PP/2R4K w - - 0 33',
expect: false,
sloppy: false,
},
{
pgn: [
'1.e4 e5 2.Nf3 d6 3.d4 Bg4 4.dxe5 Bxf3 5.Qxf3 dxe5 6.Qf5 Nc6 7.Bb5 Nge7',
'8.Qxe5 Qd7 9.O-O Nxe5 10.Bxd7+ Nxd7 11.Rd1 O-O-O 12.Nc3 Ng6 13.Be3 a6',
'14.Ba7 b6 15.Na4 Kb7 16.Bxb6 cxb6 17.b3 b5 18.Nb2 Nge5 19.f3 Rc8',
'20.Rac1 Ba3 21.Rb1 Rxc2 22.f4 Ng4 23.Rxd7+ Kc6 24.Rxf7 Bxb2 25.Rxg7',
'Ne3 26.Rg3 Bd4 27.Kh1 Rxa2 28.Rc1+ Kb6 29.e5 Rf8 30.e6 Rxf4 31.e7 Re4',
'32.Rg7 Bxg7',
],
fen: '8/4P1bp/pk6/1p6/4r3/1P2n3/r5PP/2R4K w - - 0 33',
expect: true,
sloppy: true,
},
// the sloppy PGN parser should still accept correctly disambiguated moves
{
pgn: [
'1.e4 e5 2.Nf3 d6 3.d4 Bg4 4.dxe5 Bxf3 5.Qxf3 dxe5 6.Qf5 Nc6 7.Bb5 Ne7',
'8.Qxe5 Qd7 9.O-O Nxe5 10.Bxd7+ Nxd7 11.Rd1 O-O-O 12.Nc3 Ng6 13.Be3 a6',
'14.Ba7 b6 15.Na4 Kb7 16.Bxb6 cxb6 17.b3 b5 18.Nb2 Nge5 19.f3 Rc8',
'20.Rac1 Ba3 21.Rb1 Rxc2 22.f4 Ng4 23.Rxd7+ Kc6 24.Rxf7 Bxb2 25.Rxg7',
'Ne3 26.Rg3 Bd4 27.Kh1 Rxa2 28.Rc1+ Kb6 29.e5 Rf8 30.e6 Rxf4 31.e7 Re4',
'32.Rg7 Bxg7',
],
fen: '8/4P1bp/pk6/1p6/4r3/1P2n3/r5PP/2R4K w - - 0 33',
expect: true,
sloppy: true,
},
{
pgn: [
'1.e4 e5 2.Nf3 Nc6 3.Bc4 Nf6 4.Ng5 d5 5.exd5 Nxd5 6.Nxf7 Kxf7 7.Qf3+',
'Ke6 8.Nc3 Nb4',
],
fen: 'r1bq1b1r/ppp3pp/4k3/3np3/1nB5/2N2Q2/PPPP1PPP/R1B1K2R w KQ - 4 9',
expect: true,
sloppy: true,
},
// the sloppy parser should handle lazy disambiguation (e.g. Rc1c4 below)
{
pgn: [
'1.e4 e5 2. Nf3 d5 3. Nxe5 f6 4. Bb5+ c6 5. Qh5+ Ke7',
'Qf7+ Kd6 7. d3 Kxe5 8. Qh5+ g5 9. g3 cxb5 10. Bf4+ Ke6',
'exd5+ Qxd5 12. Qe8+ Kf5 13. Rg1 gxf4 14. Nc3 Qc5 15. Ne4 Qxf2+',
'Kxf2 fxg3+ 17. Rxg3 Nd7 18. Qh5+ Ke6 19. Qe8+ Kd5 20. Rg4 Rb8',
'c4+ Kc6 22. Qe6+ Kc7 23. cxb5 Ne7 24. Rc1+ Kd8 25. Nxf6 Ra8',
'Kf1 Rb8 27. Rc1c4 b6 28. Rc4-d4 Rb7 29. Qf7 Rc7 30. Qe8# 1-0',
],
fen: '2bkQb1r/p1rnn2p/1p3N2/1P6/3R2R1/3P4/PP5P/5K2 b - - 5 30',
expect: true,
sloppy: true,
},
// sloppy parse should parse long algebraic notation
{
pgn: [
'e2e4 d7d5 e4d5 d8d5 d2d4 g8f6 c2c4 d5d8 g1f3 c8g4 f1e2 e7e6 b1c3 f8e7',
'c1e3 e8g8 d1b3 b8c6 a1d1 a8b8 e1g1 d8c8 h2h3 g4h5 d4d5 e6d5 c4d5 h5f3',
'e2f3 c6e5 f3e2 a7a6 e3a7 b8a8 a7d4 e7d6 b3c2 f8e8 f2f4 e5d7 e2d3 c7c5',
'd4f2 d6f4 c3e4 f6d5 e4d6 f4d6 d3h7',
],
fen: 'r1q1r1k1/1p1n1ppB/p2b4/2pn4/8/7P/PPQ2BP1/3R1RK1 b - - 0 25',
expect: true,
sloppy: true,
},
// sloppy parse should parse extended long san with en passant
{
pgn: [
'1. d2d4 f7f5 2. b2b3 e7e6 3. c1b2 d7d5 4. g1f3 f8d6 5. e2e3 g8f6 6. b1d2',
'e8g8 7. c2c4 c7c6 8. f1d3 b8d7 9. e1g1 f6e4 10. a1c1 g7g5 11. h2h3 d8e8 12.',
'd3e4 d5e4 13. f3g5 e8g6 14. h3h4 h7h6 15. g5h3 d7f6 16. f2f4 e4f3 17. d2f3',
'f6g4 18. d1e2 d6g3 19. h3f4 g6g7 20. d4d5 g7f7 21. d5e6 c8e6 22. f3e5 g4e5',
'23. b2e5 g8h7 24. h4h5 f8g8 25. e2f3 g3f4 26. e5f4 g8g4 27. g2g3 a8g8 28.',
'c1c2 b7b5 29. c4b5 e6d5 30. f3d1 f7h5 31. c2h2 g4g3+ 32. f4g3 g8g3+ 33.',
'g1f2 h5h2+ 34. f2e1 g3g2 35. d1d3 d5e4 36. d3d7+ h7g6 37. b5c6 g2e2+ 38.',
'e1d1 e2a2 0-1',
],
fen: '8/p2Q4/2P3kp/5p2/4b3/1P2P3/r6q/3K1R2 w - - 0 39',
expect: true,
sloppy: true,
},
// sloppy parse should parse long san with underpromotions
{
pgn: [
'1. e2e4 c7c5 2. g1f3 d7d6 3. d2d4 c5d4 4. f3d4 g8f6 5. f1d3 a7a6 6. c1e3',
'e7e5 7. d4f5 c8f5 8. e4f5 d6d5 9. e3g5 f8e7 10. d1e2 e5e4 11. g5f6 e7f6 12.',
'd3e4 d5e4 13. e2e4+ d8e7 14. e4e7+ f6e7 15. e1g1 e8g8 16. f1e1 e7f6 17.',
'c2c3 b8c6 18. b1d2 a8d8 19. d2e4 f8e8 20. e1e3 c6e5 21. a1e1 e5d3 22. e4f6+',
'g7f6 23. e3e8+ d8e8 24. e1e8+ g8g7 25. b2b4 d3e5 26. a2a4 b7b5 27. a4b5',
'a6b5 28. e8b8 e5g4 29. b8b5 g4e5 30. b5c5 g7f8 31. b4b5 f8e7 32. f2f4 e5d7',
'33. c5c7 e7d6 34. c7c8 d7b6 35. c8c6+ d6d7 36. c6b6 h7h5 37. b6f6 h5h4 38.',
'f6f7+ d7d6 39. f7h7 h4h3 40. h7h3 d6e7 41. b5b6 e7f6 42. h3h5 f6g7 43. b6b7',
'g7g8 44. b7b8N g8g7 45. c3c4 g7f6 46. c4c5 f6e7 47. c5c6 e7f6 48. c6c7 f6e7',
'49. c7c8B e7d6 50. b8a6 d6e7 51. c8e6 e7f6 52. a6c5 f6g7 53. c5e4 g7f8 54.',
'h5h8+ f8g7 55. h8g8+ g7h6 56. g8g6+ h6h7 57. e4f6+ h7h8 58. f6e4 h8h7 59.',
'f5f6 h7g6 60. f6f7 g6h5 61. f7f8R h5h6 62. f4f5 h6h7 63. f8f7+ h7h6 64.',
'f5f6 h6g6 65. f7g7+ g6h5 66. f6f7 h5h4 67. f7f8Q h4h5 68. f8h8# 1-0',
],
fen: '7Q/6R1/4B3/7k/4N3/8/6PP/6K1 b - - 2 68',
expect: true,
sloppy: true,
},
// sloppy parse should parse abbreviated long algebraic notation
{
pgn: [
'1. d2d4 f7f5 2. Bc1g5 d7d6 3. e2e3 Nb8d7 4. c2c4 Ng8f6 5. Nb1c3 e7e5 6.',
'd4e5 d6e5 7. g2g3 Bf8e7 8. Bf1h3 h7h6 9. Bg5f6 Nd7f6 10. Qd1d8+ Be7d8 11.',
'Ng1f3 e5e4 12. Nf3d4 g7g6 13. e1g1 c7c5 14. Nd4b5 e8g8 15. Nb5d6 Bd8c7 16.',
'Nd6c8 Ra8c8 17. Rf1d1 Rc8d8 18. Bh3f1 b7b6 19. Nc3d5 Nf6d5 20. c4d5 Rf8e8',
'21. Bf1b5 Re8e5 22. Bb5c6 Kg8f7 23. Kg1f1 Kf7f6 24. h2h4 g6g5 25. h4g5+',
'h6g5 26. Kf1e2 Rd8h8 27. Rd1h1 Rh8h1 28. Ra1h1 Kf6g7 29. Rh1h5 Kg7g6 30.',
'Rh5h8 Re5e7 31. Rh8a8 a7a5 32. Ra8a7 Kg6f6 33. Ra7b7 Kf6e5 34. Ke2d2 f5f4',
'35. g3f4+ g5f4 36. Kd2c3 f4e3 37. f2e3 Re7f7 38. Kc3c4 Ke5d6 39. a2a3 Rf7f3',
'40. b2b4 a5b4 41. a3b4 c5b4 42. Kc4b4 Rf3e3 43. Kb4c4 Re3a3 44. Kc4b4 e4e3',
'45. Bc6b5 Ra3a1 46. Kb4c3 Ra1a3+ 47. Kc3d4 Ra3b3 48. Bb5e2 Rb3b4+ 49. Kd4e3',
'Rb4h4 50. Be2f3 Rh4h3 51. Rb7a7 Rh3f3+ 52. Ke3f3 b6b5 53. Kf3e4 Kd6c5 54.',
'Ra7b7 Bc7b6 55. Ke4e5 b5b4 56. d5d6 b4b3 57. Rb7b6 Kc5b6 58. d6d7 Kb6c7 59.',
'Ke5e6 1-0',
],
fen: '8/2kP4/4K3/8/8/1p6/8/8 b - - 2 59',
expect: true,
sloppy: true,
},
// sloppy parse should parse extended long algebraic notation
{
pgn: [
'1. e2-e4 c7-c5 2. Ng1-f3 d7-d6 3. d2-d4 c5xd4 4. Nf3xd4 Ng8-f6 5. Bf1-d3',
'a7-a6 6. Bc1-e3 e7-e5 7. Nd4-f5 Bc8xf5 8. e4xf5 d6-d5 9. Be3-g5 Bf8-e7 10.',
'Qd1-e2 e5-e4 11. Bg5xf6 Be7xf6 12. Bd3xe4 d5xe4 13. Qe2xe4+ Qd8-e7 14.',
'Qe4xe7+ Bf6xe7 15. e1-g1 e8-g8 16. Rf1-e1 Be7-f6 17. c2-c3 Nb8-c6 18.',
'Nb1-d2 Ra8-d8 19. Nd2-e4 Rf8-e8 20. Re1-e3 Nc6-e5 21. Ra1-e1 Ne5-d3 22.',
'Ne4xf6+ g7xf6 23. Re3xe8+ Rd8xe8 24. Re1xe8+ Kg8-g7 25. b2-b4 Nd3-e5 26.',
'a2-a4 b7-b5 27. a4xb5 a6xb5 28. Re8-b8 Ne5-g4 29. Rb8xb5 Ng4-e5 30. Rb5-c5',
'Kg7-f8 31. b4-b5 Kf8-e7 32. f2-f4 Ne5-d7 33. Rc5-c7 Ke7-d6 34. Rc7-c8',
'Nd7-b6 35. Rc8-c6+ Kd6-d7 36. Rc6xb6 h7-h5 37. Rb6xf6 h5-h4 38. Rf6xf7+',
'Kd7-d6 39. Rf7-h7 h4-h3 40. Rh7xh3 Kd6-e7 41. b5-b6 Ke7-f6 42. Rh3-h5',
'Kf6-g7 43. b6-b7 Kg7-g8 44. b7-b8N Kg8-g7 45. c3-c4 Kg7-f6 46. c4-c5 Kf6-e7',
'47. c5-c6 Ke7-f6 48. c6-c7 Kf6-e7 49. c7-c8B Ke7-d6 50. Nb8-a6 Kd6-e7 51.',
'Bc8-e6 Ke7-f6 52. Na6-c5 Kf6-g7 53. Nc5-e4 Kg7-f8 54. Rh5-h8+ Kf8-g7 55.',
'Rh8-g8+ Kg7-h6 56. Rg8-g6+ Kh6-h7 57. Ne4-f6+ Kh7-h8 58. Nf6-e4 Kh8-h7 59.',
'f5-f6 Kh7xg6 60. f6-f7 Kg6-h5 61. f7-f8R Kh5-h6 62. f4-f5 Kh6-h7 63.',
'Rf8-f7+ Kh7-h6 64. f5-f6 Kh6-g6 65. Rf7-g7+ Kg6-h5 66. f6-f7 Kh5-h4 67.',
'f7-f8Q Kh4-h5 68. Qf8-h8# 1-0',
],
fen: '7Q/6R1/4B3/7k/4N3/8/6PP/6K1 b - - 2 68',
expect: true,
sloppy: true,
},
{
// strict parser - FEN requires a corresponding SetUp tag
fen: '1n1Rkb1r/p4ppp/4q3/4p1B1/4P3/8/PPP2PPP/2K5 b k - 1 17',
pgn: [
'[White "Paul Morphy"]',
'[Black "Duke Karl / Count Isouard"]',
'[FEN "1n2kb1r/p4ppp/4q3/4p1B1/4P3/8/PPP2PPP/2KR4 w k - 0 17"]',
'',
'17.Rd8# 1-0',
],
expect: false,
},
{
// sloppy parser - FEN doesn't need a SetUp tag
fen: '1n1Rkb1r/p4ppp/4q3/4p1B1/4P3/8/PPP2PPP/2K5 b k - 1 17',
pgn: [
'[White "Paul Morphy"]',
'[Black "Duke Karl / Count Isouard"]',
'[FEN "1n2kb1r/p4ppp/4q3/4p1B1/4P3/8/PPP2PPP/2KR4 w k - 0 17"]',
'',
'17.Rd8# 1-0',
],
sloppy: true,
expect: true,
},
{
// sloppy parser - FEN case doesn't matter
fen: '1n1Rkb1r/p4ppp/4q3/4p1B1/4P3/8/PPP2PPP/2K5 b k - 1 17',
pgn: [
'[White "Paul Morphy"]',
'[Black "Duke Karl / Count Isouard"]',
'[feN "1n2kb1r/p4ppp/4q3/4p1B1/4P3/8/PPP2PPP/2KR4 w k - 0 17"]',
'',
'17.Rd8# 1-0',
],
sloppy: true,
expect: true,
}
];
const newlines = ['\n', '<br />', '\r\n', 'BLAH'];
for (let i = 0; i < tests.length; i++) {
const t = tests[i];
for (let j = 0; j < newlines.length; j++) {
const newline = newlines[j];
test(i + String.fromCharCode(97 + j), () => {
const sloppy = t.sloppy || false;
const result = chess.pgn(t.pgn.join(newline), { endings: newline, sloppy });
const should_pass = t.expect;
// some tests are expected to fail
expect(result).toBe(should_pass);
if (should_pass) {
// some PGN's tests contain comments which are stripped
// during parsing, so we'll need compare the results of
// the load against a FEN string (instead of the PGN)
if ('fen' in t) expect(chess.fen()).toBe(t.fen);
else expect(chess.pgn({ wrap: 65, endings: newline }))
.toBe(t.pgn.join(newline));
}
});
};
}
test('DRTYF', () => {
// special case dirty file containing a mix of \n and \r\n
const pgn = '[Event "Reykjavik WCh"]\n' +
'[Site "Reykjavik WCh"]\n' +
'[Date "1972.01.07"]\n' +
'[EventDate "?"]\n' +
'[Round "6"]\n' +
'[Result "1-0"]\n' +
'[White "Robert James Fischer"]\r\n' +
'[Black "Boris Spassky"]\n' +
'[ECO "D59"]\n' +
'[WhiteElo "?"]\n' +
'[BlackElo "?"]\n' +
'[PlyCount "81"]\n' +
'\r\n' +
'1. c4 e6 2. Nf3 d5 3. d4 Nf6 4. Nc3 Be7 5. Bg5 O-O 6. e3 h6\n' +
'7. Bh4 b6 8. cxd5 Nxd5 9. Bxe7 Qxe7 10. Nxd5 exd5 11. Rc1 Be6\n' +
'12. Qa4 c5 13. Qa3 Rc8 14. Bb5 a6 15. dxc5 bxc5 16. O-O Ra7\n' +
'17. Be2 Nd7 18. Nd4 Qf8 19. Nxe6 fxe6 20. e4 d4 21. f4 Qe7\r\n' +
'22. e5 Rb8 23. Bc4 Kh8 24. Qh3 Nf8 25. b3 a5 26. f5 exf5\n' +
'27. Rxf5 Nh7 28. Rcf1 Qd8 29. Qg3 Re7 30. h4 Rbb7 31. e6 Rbc7\n' +
'32. Qe5 Qe8 33. a4 Qd8 34. R1f2 Qe8 35. R2f3 Qd8 36. Bd3 Qe8\n' +
'37. Qe4 Nf6 38. Rxf6 gxf6 39. Rxf6 Kg8 40. Bc4 Kh8 41. Qf4 1-0\n';
const result = chess.pgn(pgn, { endings: '\r?\n' });
assert(result);
assert(chess.pgn(pgn));
expect(chess.pgn().match(/^\[\[/)).toBe(null);
});
test('DRTYW', () => {
// dirty pgn whitespace
const pgn = ' \t [Event"Reykjavik WCh"]\n' +
'[Site "Reykjavik WCh"] \n' +
'[Date "1972.01.07"]\n' +
'[EventDate "?"]\n' +
'[Round "6"]\n' +
'[Result "1-0"]\n' +
'[White "Robert James Fischer"]\r\n' +
'[Black "Boris Spassky"]\n' +
'[ECO "D59"]\n' +
'[WhiteElo "?"]\n' +
'[BlackElo "?"]\n' +
'[PlyCount "81"] \n' +
' \r\n' +
'1. c4 e6 2. Nf3 d5 3. d4 Nf6 4. Nc3 Be7 5. Bg5 O-O 6. e3 h6\n' +
'7. Bh4 b6 8. cxd5 Nxd5 9. Bxe7 Qxe7 10. Nxd5 exd5 11. Rc1 Be6\n' +
'12. Qa4 c5 13. Qa3 Rc8 14. Bb5 a6 15. dxc5 bxc5 16. O-O Ra7\n' +
'17. Be2 Nd7 18. Nd4 Qf8 19. Nxe6 fxe6 20. e4 d4 21. f4 Qe7\r\n' +
'22. e5 Rb8 23. Bc4 Kh8 24. Qh3 Nf8 25. b3 a5 26. f5 exf5\n' +
'27. Rxf5 Nh7 28. Rcf1 Qd8 29. Qg3 Re7 30. h4 Rbb7 31. e6 Rbc7\n' +
'32. Qe5 Qe8 33. a4 Qd8 34. R1f2 Qe8 35. R2f3 Qd8 36. Bd3 Qe8\n' +
'37. Qe4 Nf6 38. Rxf6 gxf6 39. Rxf6 Kg8 40. Bc4 Kh8 41. Qf4 1-0\n';
const result = chess.pgn(pgn, { endings: '\r?\n' });
assert(result);
assert(chess.pgn(pgn));
expect(chess.pgn().match(/^\[\[/)).toBe(null);
});
});
test('MANCO', () => {
/// unpacks the comment dictionary in
/// [{ fen: string, comment: string }]
const unpack = dictionary => {
const result = [];
for (const fen in dictionary)
result.push({ fen, comment: dictionary[fen] });
return result;
};
// manipulate comments
test('NOCOM', () => {
// no comments
const chess = new Chess();
expect(chess.comment()).toBe(undefined);
expect(unpack(chess.comments())).toEqual([]);
chess.move('e4');
expect(chess.comment()).toBe(undefined);
expect(unpack(chess.comments())).toEqual([]);
expect(chess.pgn()).toBe('1. e4');
});
test('INICO', () => {
// initial position comment
const chess = new Chess();