chess-moments 0.14.0 → 0.14.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.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Always returns an array because there can be more than one next moment
3
+ */
4
+ const getNextMoments = (moments, current) => {
5
+ try {
6
+ const next = [];
7
+ const sameDepth = moments[moments.indexOf(current) + 1];
8
+ if (sameDepth.move && sameDepth.depth === current.depth) {
9
+ next.push(sameDepth);
10
+ }
11
+
12
+ // If it's the first moment without move, add the next moment
13
+ if (current.index === 0 && !current.move) {
14
+ next.push(moments[1]);
15
+ }
16
+
17
+ // Split moments after the current index
18
+ const currentIndex = moments.indexOf(current);
19
+ const moves = moments.slice(currentIndex + 1).filter((m) => m.move);
20
+
21
+ // Add fullmove number to the current moment
22
+ current.fullmove = Number(current.fen.split(' ')[5]);
23
+
24
+ // Active color is white
25
+ if (current.fen.split(' ')[1] === 'w') {
26
+ for (const moment of moves) {
27
+ const activeColor = moment.fen.split(' ')[1];
28
+ const fullmove = Number(moment.fen.split(' ')[5]);
29
+
30
+ // Check if the next moment is a valid move for white
31
+ if (
32
+ moment.depth === current.depth + 1 &&
33
+ activeColor === 'b' &&
34
+ fullmove === current.fullmove
35
+ ) {
36
+ next.push(moment);
37
+ }
38
+ }
39
+ }
40
+
41
+ // Active color is black
42
+ if (current.fen.split(' ')[1] === 'b') {
43
+ for (const moment of moves) {
44
+ const activeColor = moment.fen.split(' ')[1];
45
+ const fullmove = Number(moment.fen.split(' ')[5]);
46
+
47
+ // Check if the next moment is a valid move for black
48
+ if (
49
+ moment.depth === current.depth + 1 &&
50
+ activeColor === 'w' &&
51
+ fullmove === current.fullmove + 1
52
+ ) {
53
+ next.push(moment);
54
+ }
55
+ }
56
+ }
57
+
58
+ // If no next moment is found, return an empty array
59
+ return next;
60
+ } catch {
61
+ return [];
62
+ }
63
+ };
64
+
65
+ module.exports = getNextMoments;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Always returns an object because there can be only one previous moment
3
+ */
4
+ const getPrevMoment = (moments, current) => {
5
+ try {
6
+ const sameDepth = moments[moments.indexOf(current) - 1];
7
+ if (sameDepth.move && sameDepth.depth === current.depth) {
8
+ return sameDepth;
9
+ }
10
+
11
+ // If it's the first moment with move, return the previous moment (starting position)
12
+ if (current.index === 1 && current.move) {
13
+ return moments[0];
14
+ }
15
+
16
+ // Split moments until the current index
17
+ const currentIndex = moments.indexOf(current);
18
+ const moves = moments.slice(0, currentIndex).filter((m) => m.move);
19
+
20
+ // Add fullmove number to the current moment
21
+ current.fullmove = Number(current.fen.split(' ')[5]);
22
+
23
+ // Active color is white
24
+ if (current.fen.split(' ')[1] === 'w') {
25
+ // Traverse moves in reverse order to optimize search
26
+ for (let i = moves.length - 1; i >= 0; i--) {
27
+ const moment = moves[i];
28
+ const activeColor = moment.fen.split(' ')[1];
29
+ const fullmove = Number(moment.fen.split(' ')[5]);
30
+
31
+ if (
32
+ moment.depth === current.depth - 1 &&
33
+ activeColor === 'b' &&
34
+ fullmove === current.fullmove - 1
35
+ ) {
36
+ return moment;
37
+ }
38
+ }
39
+ }
40
+
41
+ // Active color is black
42
+ if (current.fen.split(' ')[1] === 'b') {
43
+ // Traverse moves in reverse order to optimize search
44
+ for (let i = moves.length - 1; i >= 0; i--) {
45
+ const moment = moves[i];
46
+ const activeColor = moment.fen.split(' ')[1];
47
+ const fullmove = Number(moment.fen.split(' ')[5]);
48
+
49
+ if (
50
+ moment.depth === current.depth - 1 &&
51
+ activeColor === 'w' &&
52
+ fullmove === current.fullmove
53
+ ) {
54
+ return moment;
55
+ }
56
+ }
57
+ }
58
+
59
+ // If no previous moment is found, return an empty object
60
+ return {};
61
+ } catch {
62
+ return {};
63
+ }
64
+ };
65
+
66
+ module.exports = getPrevMoment;
@@ -0,0 +1,11 @@
1
+ const getNextMoments = require('./get-next-moments');
2
+ const getPrevMoment = require('./get-prev-moment');
3
+ const momentsToPgn = require('./moments-to-pgn');
4
+ const moveTrainer = require('./move-trainer');
5
+
6
+ module.exports = {
7
+ getNextMoments,
8
+ getPrevMoment,
9
+ momentsToPgn,
10
+ moveTrainer,
11
+ };
@@ -0,0 +1,158 @@
1
+ const fen = require('../fen');
2
+ const { getBrushCode } = require('../helpers');
3
+
4
+ /**
5
+ * Converts chess moments back to PGN format
6
+ * @param {Array} moments - Array of chess moment objects
7
+ * @returns {String} PGN string
8
+ */
9
+ const momentsToPgn = (moments) => {
10
+ if (!Array.isArray(moments) || moments.length === 0) {
11
+ return '';
12
+ }
13
+
14
+ let pgn = '';
15
+ let currentDepth = 1;
16
+ let variationStack = [];
17
+
18
+ // Check if we need to add FEN headers for non-standard starting position
19
+ const initialMoment = moments[0];
20
+ if (initialMoment && initialMoment.fen && initialMoment.fen !== fen.initial) {
21
+ pgn += `[SetUp "1"]\n`;
22
+ pgn += `[FEN "${initialMoment.fen}"]\n\n`;
23
+ }
24
+
25
+ for (let i = 0; i < moments.length; i++) {
26
+ const moment = moments[i];
27
+ const depth = moment.depth || 1;
28
+
29
+ // Handle moments without moves (initial position or variation breaks)
30
+ if (!moment.move) {
31
+ // If this is a variation marker (depth > 1) and not the initial position
32
+ if (depth > 1 && i > 0) {
33
+ // Check if we need to start a new variation at the same depth
34
+ if (depth === currentDepth) {
35
+ // Close current variation and start a new one at the same level
36
+ pgn += ') (';
37
+ } else if (depth > currentDepth) {
38
+ // Starting a new variation at a deeper level
39
+ variationStack.push(currentDepth);
40
+ pgn += ' (';
41
+ currentDepth = depth;
42
+ }
43
+ }
44
+
45
+ // Add initial comment if present
46
+ if (moment.comment) {
47
+ pgn += `{${moment.comment}} `;
48
+ }
49
+ continue;
50
+ }
51
+
52
+ // Determine who made this move and what move number it is from the FEN
53
+ const fenParts = moment.fen.split(' ');
54
+ const activeColorAfterMove = fenParts[1]; // 'w' or 'b' - who moves next
55
+ const fullmoveNumber = parseInt(fenParts[5]);
56
+
57
+ // If black is to move after this move, then white made this move
58
+ const moveWasByWhite = activeColorAfterMove === 'b';
59
+ // For white moves, the move number is the fullmove number
60
+ // For black moves, the move number is fullmove number - 1 (since fullmove increments after black's move)
61
+ const moveNumber = moveWasByWhite ? fullmoveNumber : fullmoveNumber - 1;
62
+
63
+ // Handle depth changes (variations)
64
+ if (depth > currentDepth) {
65
+ // Starting a new variation
66
+ variationStack.push(currentDepth);
67
+ pgn += ' (';
68
+ currentDepth = depth;
69
+ } else if (depth < currentDepth) {
70
+ // Ending variation(s) - close as many as needed to get to the right depth
71
+ while (currentDepth > depth) {
72
+ variationStack.pop();
73
+ pgn += ')';
74
+ currentDepth--;
75
+ }
76
+ if (pgn.endsWith(')')) {
77
+ pgn += ' ';
78
+ }
79
+ }
80
+
81
+ // Add move number for white moves or when starting a variation
82
+ if (moveWasByWhite || (depth > 1 && pgn.endsWith('('))) {
83
+ pgn += `${moveNumber}.`;
84
+ if (!moveWasByWhite) {
85
+ pgn += '..';
86
+ }
87
+ pgn += ' ';
88
+ }
89
+
90
+ // Add the move
91
+ pgn += moment.move;
92
+
93
+ // Add comment if present
94
+ if (moment.comment) {
95
+ pgn += ` {${moment.comment}}`;
96
+ }
97
+
98
+ // Add shapes if present
99
+ if (moment.shapes && moment.shapes.length > 0) {
100
+ let shapesComment = '';
101
+ const squareHighlights = moment.shapes.filter(
102
+ (s) => !s.dest || s.dest === ']'
103
+ );
104
+ const arrows = moment.shapes.filter((s) => s.dest && s.dest !== ']');
105
+
106
+ if (squareHighlights.length > 0) {
107
+ const coloredSquares = squareHighlights
108
+ .map((s) => {
109
+ const colorCode = getBrushCode(s.brush);
110
+ return `${colorCode}${s.orig}`;
111
+ })
112
+ .join(',');
113
+ shapesComment += `[%csl ${coloredSquares}]`;
114
+ }
115
+
116
+ if (arrows.length > 0) {
117
+ const coloredArrows = arrows
118
+ .map((s) => {
119
+ const colorCode = getBrushCode(s.brush);
120
+ return `${colorCode}${s.orig}${s.dest}`;
121
+ })
122
+ .join(',');
123
+ if (shapesComment) shapesComment += ' ';
124
+ shapesComment += `[%cal ${coloredArrows}]`;
125
+ }
126
+
127
+ if (shapesComment) {
128
+ if (moment.comment) {
129
+ // If there's already a comment, add shapes to it
130
+ pgn = pgn.replace(/\{([^}]*)\}$/, `{$1 ${shapesComment}}`);
131
+ } else {
132
+ pgn += ` {${shapesComment}}`;
133
+ }
134
+ }
135
+ }
136
+
137
+ // Add space after move if not the last move
138
+ if (i < moments.length - 1) {
139
+ const nextMoment = moments[i + 1];
140
+ if (nextMoment && nextMoment.move) {
141
+ pgn += ' ';
142
+ }
143
+ }
144
+ }
145
+
146
+ // Close any remaining variations
147
+ while (variationStack.length > 0) {
148
+ variationStack.pop();
149
+ pgn += ')';
150
+ }
151
+
152
+ // Add default result
153
+ pgn += ' *';
154
+
155
+ return pgn.trim();
156
+ };
157
+
158
+ module.exports = momentsToPgn;
@@ -1,8 +1,8 @@
1
1
  const { flatten } = require('lodash');
2
- const splitByMoveIndex = require('./split-by-move');
3
- const tree = require('./tree');
2
+ const splitByMoveIndex = require('../split-by-move');
3
+ const tree = require('../tree');
4
4
 
5
- const train = (sloppyPgn, fen) => {
5
+ const moveTrainer = (sloppyPgn, fen) => {
6
6
  // Tree is an array of array depths
7
7
  const moments = tree(sloppyPgn);
8
8
 
@@ -52,4 +52,4 @@ const train = (sloppyPgn, fen) => {
52
52
  return training;
53
53
  };
54
54
 
55
- module.exports = train;
55
+ module.exports = moveTrainer;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Convert brush color name to PGN color code
3
+ */
4
+ const getBrushCode = (brush) => {
5
+ const brushCodes = {
6
+ green: 'G',
7
+ red: 'R',
8
+ blue: 'B',
9
+ yellow: 'Y',
10
+ };
11
+ return brushCodes[brush] || 'G';
12
+ };
13
+
14
+ module.exports = getBrushCode;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Convert PGN color code to brush color name
3
+ */
4
+ const getBrushColor = (letter) => {
5
+ const brushes = {
6
+ R: 'red',
7
+ Y: 'yellow',
8
+ G: 'green',
9
+ B: 'blue',
10
+ };
11
+
12
+ return brushes[letter] || 'green';
13
+ };
14
+
15
+ module.exports = getBrushColor;
@@ -0,0 +1,7 @@
1
+ const getBrushCode = require('./get-brush-code');
2
+ const getBrushColor = require('./get-brush-color');
3
+
4
+ module.exports = {
5
+ getBrushCode,
6
+ getBrushColor,
7
+ };
@@ -8,7 +8,6 @@ const prepare = require('./prepare');
8
8
  const prettify = require('./prettify');
9
9
  const shape = require('./shape');
10
10
  const split = require('./split');
11
- const train = require('./train');
12
11
  const tree = require('./tree');
13
12
 
14
13
  module.exports = {
@@ -22,6 +21,5 @@ module.exports = {
22
21
  prettify,
23
22
  shape,
24
23
  split,
25
- train,
26
24
  tree,
27
25
  };
package/index.js CHANGED
@@ -1,17 +1,18 @@
1
- const { fen, pgn, flat, train, tree } = require('./functions');
1
+ const { fen, pgn, flat, tree } = require('./functions');
2
2
  const {
3
3
  getNextMoments,
4
4
  getPrevMoment,
5
5
  momentsToPgn,
6
+ moveTrainer,
6
7
  } = require('./functions/extras');
7
8
 
8
9
  module.exports = {
9
10
  fen,
10
11
  pgn,
11
12
  flat,
12
- train,
13
13
  tree,
14
14
  getNextMoments,
15
15
  getPrevMoment,
16
16
  momentsToPgn,
17
+ moveTrainer,
17
18
  };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "chess-moments",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "PGN parser that transforms PGN files into chess \"moments\"",
5
5
  "main": "dist/index.js",
6
6
  "files": [
7
7
  "index.js",
8
8
  "chess.js",
9
- "functions/*.js"
9
+ "functions/**/*.js"
10
10
  ],
11
11
  "scripts": {
12
12
  "test": "mocha",