chess-moments 0.15.2 → 1.1.0
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/functions/extras/add-moment-to-tree.js +66 -0
- package/functions/extras/index.js +2 -0
- package/functions/flat.js +13 -9
- package/functions/helpers/index.js +4 -0
- package/functions/helpers/insert-moment-into-tree.js +97 -0
- package/functions/helpers/prepare-moment.js +34 -0
- package/functions/make-moments.js +6 -6
- package/functions/normalize-pgn.js +3 -0
- package/functions/parser.js +8 -9
- package/functions/previous-fen.js +13 -10
- package/functions/tree.js +12 -8
- package/index.js +2 -0
- package/package.json +2 -2
- package/chess.js +0 -15
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { flatten } = require('lodash');
|
|
2
|
+
const { insertMomentIntoTree } = require('../helpers');
|
|
3
|
+
|
|
4
|
+
const addMomentToTree = (tree, newMoment) => {
|
|
5
|
+
if (!newMoment || !newMoment.san || !newMoment.before) {
|
|
6
|
+
return tree;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const flat = flatten(tree);
|
|
10
|
+
// Check if moment FEN already exists in the tree
|
|
11
|
+
if (flat.some((moment) => moment.fen === newMoment.after)) {
|
|
12
|
+
return tree;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Find the appropriate position to insert the moment
|
|
16
|
+
let point = null;
|
|
17
|
+
|
|
18
|
+
// Look for the position where this moment should be inserted
|
|
19
|
+
// by finding the moment with matching "before" FEN at any depth
|
|
20
|
+
for (let lineIndex = 0; lineIndex < tree.length; lineIndex++) {
|
|
21
|
+
const line = tree[lineIndex];
|
|
22
|
+
|
|
23
|
+
for (let momentIndex = 0; momentIndex < line.length; momentIndex++) {
|
|
24
|
+
const existingMoment = line[momentIndex];
|
|
25
|
+
|
|
26
|
+
// If we find a moment with the same "before" FEN, this is where we branch
|
|
27
|
+
if (existingMoment.fen === newMoment.before) {
|
|
28
|
+
// Check if there's already a move after this position in the same line
|
|
29
|
+
const nextMomentInLine = line[momentIndex + 1];
|
|
30
|
+
|
|
31
|
+
if (nextMomentInLine && nextMomentInLine.move) {
|
|
32
|
+
// There's already a move after this position, so create a new sideline
|
|
33
|
+
point = {
|
|
34
|
+
type: 'newLine',
|
|
35
|
+
afterLineIndex: lineIndex,
|
|
36
|
+
branchMomentIndex: momentIndex,
|
|
37
|
+
};
|
|
38
|
+
} else {
|
|
39
|
+
// No move after this position, so continue in the same line
|
|
40
|
+
point = { type: 'sameLine', lineIndex, momentIndex: momentIndex + 1 };
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// If we found an insertion point, no need to continue searching
|
|
47
|
+
if (point) {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Insert the moment into the tree
|
|
53
|
+
const newTree = insertMomentIntoTree(tree, point, newMoment);
|
|
54
|
+
|
|
55
|
+
// Reindex all moments
|
|
56
|
+
let globalIndex = 0;
|
|
57
|
+
for (const line of newTree) {
|
|
58
|
+
for (const moment of line) {
|
|
59
|
+
moment.index = globalIndex++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return newTree;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
module.exports = addMomentToTree;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
const addMomentToTree = require('./add-moment-to-tree');
|
|
1
2
|
const getNextMoments = require('./get-next-moments');
|
|
2
3
|
const getPrevMoment = require('./get-prev-moment');
|
|
3
4
|
const momentsToPgn = require('./moments-to-pgn');
|
|
4
5
|
const moveTrainer = require('./move-trainer');
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
8
|
+
addMomentToTree,
|
|
7
9
|
getNextMoments,
|
|
8
10
|
getPrevMoment,
|
|
9
11
|
momentsToPgn,
|
package/functions/flat.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
const makeMoments = require('./make-moments');
|
|
2
2
|
|
|
3
3
|
const flat = (sloppyPgn) => {
|
|
4
|
-
|
|
4
|
+
try {
|
|
5
|
+
const moments = makeMoments(sloppyPgn);
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
// flatten array and add index for every moment
|
|
8
|
+
const flatten = [].concat.apply([], moments);
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
let index = 0;
|
|
11
|
+
for (const moment of flatten) {
|
|
12
|
+
moment.index = index;
|
|
13
|
+
index++;
|
|
14
|
+
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
return flatten;
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
module.exports = flat;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
const getBrushCode = require('./get-brush-code');
|
|
2
2
|
const getBrushColor = require('./get-brush-color');
|
|
3
3
|
const getMoveNumber = require('./get-move-number');
|
|
4
|
+
const insertMomentIntoTree = require('./insert-moment-into-tree');
|
|
5
|
+
const prepareMoment = require('./prepare-moment');
|
|
4
6
|
|
|
5
7
|
module.exports = {
|
|
6
8
|
getBrushCode,
|
|
7
9
|
getBrushColor,
|
|
8
10
|
getMoveNumber,
|
|
11
|
+
insertMomentIntoTree,
|
|
12
|
+
prepareMoment,
|
|
9
13
|
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const prepareMoment = require('./prepare-moment');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Inserts a new moment into the tree at the specified point.
|
|
5
|
+
* This function handles both inserting into existing lines and creating new sidelines.
|
|
6
|
+
* @param {Array} tree - The current tree structure.
|
|
7
|
+
* @param {Object} point - The point in the tree where the moment will be inserted.
|
|
8
|
+
* @param {Object} newMoment - The new moment to be inserted, containing properties like san, fen, from, to.
|
|
9
|
+
* @returns {Array} Updated tree with the new moment inserted
|
|
10
|
+
*/
|
|
11
|
+
const insertMomentIntoTree = (tree, point, newMoment) => {
|
|
12
|
+
// Prepare the moment to be inserted with only the properties that match normal moments
|
|
13
|
+
const momentToInsert = prepareMoment(tree, point, newMoment);
|
|
14
|
+
|
|
15
|
+
// If no matching position found, always create a new line
|
|
16
|
+
if (!point) {
|
|
17
|
+
tree.push([momentToInsert]);
|
|
18
|
+
return tree;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Insert into existing line
|
|
22
|
+
if (point.type === 'sameLine') {
|
|
23
|
+
tree[point.lineIndex].splice(point.momentIndex, 0, momentToInsert);
|
|
24
|
+
return tree;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Create new sideline
|
|
28
|
+
if (point.type === 'newLine') {
|
|
29
|
+
// Check if we need to split the mainline to create proper structure
|
|
30
|
+
const originalMainline = tree[point.afterLineIndex];
|
|
31
|
+
const branchMomentIndex = point.branchMomentIndex;
|
|
32
|
+
|
|
33
|
+
// Find where to split: look for moves that come after the branch point sequence
|
|
34
|
+
let splitIndex = branchMomentIndex + 1;
|
|
35
|
+
|
|
36
|
+
// Skip the immediate next move(s) that continue from the branch point
|
|
37
|
+
// and find moves that should be in a separate continuation
|
|
38
|
+
while (splitIndex < originalMainline.length) {
|
|
39
|
+
const currentMoment = originalMainline[splitIndex];
|
|
40
|
+
if (currentMoment.move) {
|
|
41
|
+
// For now, assume the next move continues the sequence
|
|
42
|
+
// and the move after that should be separated
|
|
43
|
+
let nextMoveIndex = splitIndex + 1;
|
|
44
|
+
while (
|
|
45
|
+
nextMoveIndex < originalMainline.length &&
|
|
46
|
+
!originalMainline[nextMoveIndex].move
|
|
47
|
+
) {
|
|
48
|
+
nextMoveIndex++;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (nextMoveIndex < originalMainline.length) {
|
|
52
|
+
// There's another move after this one, split here for continuation
|
|
53
|
+
splitIndex = nextMoveIndex;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
splitIndex++;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (splitIndex < originalMainline.length) {
|
|
61
|
+
// Split the mainline
|
|
62
|
+
const beforeSplit = originalMainline.slice(0, splitIndex);
|
|
63
|
+
const afterSplit = originalMainline.slice(splitIndex);
|
|
64
|
+
|
|
65
|
+
// Update the original mainline to keep the sequence up to the split
|
|
66
|
+
tree[point.afterLineIndex] = beforeSplit;
|
|
67
|
+
|
|
68
|
+
// Insert the sideline after the mainline
|
|
69
|
+
const sidelinePositionMarker = {
|
|
70
|
+
depth: momentToInsert.depth,
|
|
71
|
+
fen: newMoment.before,
|
|
72
|
+
};
|
|
73
|
+
const sidelineLine = [sidelinePositionMarker, momentToInsert];
|
|
74
|
+
tree.splice(point.afterLineIndex + 1, 0, sidelineLine);
|
|
75
|
+
|
|
76
|
+
// Insert the continuation line after the sideline
|
|
77
|
+
const continuationPositionMarker = {
|
|
78
|
+
depth: beforeSplit[0]?.depth || 1, // Use the depth of the line being continued
|
|
79
|
+
fen: beforeSplit[beforeSplit.length - 1].fen,
|
|
80
|
+
};
|
|
81
|
+
const continuationLine = [continuationPositionMarker, ...afterSplit];
|
|
82
|
+
tree.splice(point.afterLineIndex + 2, 0, continuationLine);
|
|
83
|
+
} else {
|
|
84
|
+
// No continuation needed, just add the sideline
|
|
85
|
+
const sidelinePositionMarker = {
|
|
86
|
+
depth: momentToInsert.depth,
|
|
87
|
+
fen: newMoment.before,
|
|
88
|
+
};
|
|
89
|
+
const sidelineLine = [sidelinePositionMarker, momentToInsert];
|
|
90
|
+
tree.splice(point.afterLineIndex + 1, 0, sidelineLine);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return tree;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
module.exports = insertMomentIntoTree;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepares a moment object for insertion into the tree.
|
|
3
|
+
* It ensures the moment has the correct properties and depth based on its position.
|
|
4
|
+
* @param {Array} tree - The current tree structure.
|
|
5
|
+
* @param {Object} point - The point in the tree where the moment will be inserted.
|
|
6
|
+
* @param {Object} newMoment - The new moment to be inserted, containing properties like san, fen, from, to.
|
|
7
|
+
* @returns {Object} Prepared moment object with depth and necessary properties
|
|
8
|
+
*/
|
|
9
|
+
const prepareMoment = (tree, point, newMoment) => {
|
|
10
|
+
let targetDepth = newMoment.depth || 1;
|
|
11
|
+
|
|
12
|
+
if (point) {
|
|
13
|
+
if (point.type === 'sameLine' && point.lineIndex !== undefined) {
|
|
14
|
+
// Use the depth of the line we're inserting into
|
|
15
|
+
targetDepth = tree[point.lineIndex]?.[0]?.depth || 1;
|
|
16
|
+
} else if (point.type === 'newLine') {
|
|
17
|
+
// For new sidelines, increment the depth from where we're branching
|
|
18
|
+
const branchingLine = tree[point.afterLineIndex];
|
|
19
|
+
const branchingMoment = branchingLine?.[point.branchMomentIndex];
|
|
20
|
+
const branchingDepth = branchingMoment?.depth || 1;
|
|
21
|
+
targetDepth = branchingDepth + 1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
depth: targetDepth,
|
|
27
|
+
fen: newMoment.after,
|
|
28
|
+
move: newMoment.san,
|
|
29
|
+
from: newMoment.from,
|
|
30
|
+
to: newMoment.to,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
module.exports = prepareMoment;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const Chess = require('
|
|
1
|
+
const { Chess } = require('chess.js');
|
|
2
2
|
const fen = require('./fen');
|
|
3
3
|
const parser = require('./parser');
|
|
4
4
|
const pgn = require('./pgn');
|
|
@@ -8,12 +8,12 @@ const make = (sloppyPgn) => {
|
|
|
8
8
|
const normalizedPgn = pgn.normalize(sloppyPgn);
|
|
9
9
|
|
|
10
10
|
// load PGN and check headers for existing FEN
|
|
11
|
-
const chess = new Chess();
|
|
12
|
-
chess.
|
|
13
|
-
const
|
|
11
|
+
const chess = new Chess(); // can throw if PGN is invalid
|
|
12
|
+
chess.loadPgn(normalizedPgn);
|
|
13
|
+
const headers = chess.getHeaders();
|
|
14
14
|
|
|
15
15
|
const store = {
|
|
16
|
-
fen: fen.normalize(
|
|
16
|
+
fen: fen.normalize(headers.FEN) || fen.initial, // current FEN
|
|
17
17
|
depth: 1, // current depth
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -26,7 +26,7 @@ const make = (sloppyPgn) => {
|
|
|
26
26
|
store.depth = depth;
|
|
27
27
|
|
|
28
28
|
// parse current moves with the computed FEN
|
|
29
|
-
const moments = parser(
|
|
29
|
+
const moments = parser(moves, store.fen, depth);
|
|
30
30
|
|
|
31
31
|
// set history for the current depth
|
|
32
32
|
history.set(depth, fen.history(moments, history, depth));
|
package/functions/parser.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
+
const { Chess } = require('chess.js');
|
|
1
2
|
const { initial } = require('./fen');
|
|
2
3
|
const pgn = require('./pgn');
|
|
3
4
|
const moment = require('./moment');
|
|
4
5
|
|
|
5
|
-
module.exports = (
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
return [];
|
|
9
|
-
}
|
|
6
|
+
module.exports = (moves, fen = initial, depth = 1) => {
|
|
7
|
+
const chess = new Chess();
|
|
8
|
+
chess.loadPgn(pgn.build(moves, fen)); // can throw if PGN is invalid
|
|
10
9
|
|
|
11
10
|
const history = chess.history({ verbose: true });
|
|
12
11
|
while (chess.undo()) {
|
|
13
12
|
chess.undo();
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const comment = chess.
|
|
15
|
+
const comment = chess.getComment();
|
|
17
16
|
const first = moment.build({ depth, comment, fen: chess.fen() });
|
|
18
17
|
|
|
19
18
|
const moments = history.map((item) => {
|
|
@@ -24,14 +23,14 @@ module.exports = (chess, moves, fen = initial, depth = 1) => {
|
|
|
24
23
|
move: item.san,
|
|
25
24
|
from: item.from,
|
|
26
25
|
to: item.to,
|
|
27
|
-
comment: chess.
|
|
26
|
+
comment: chess.getComment(),
|
|
28
27
|
fen: chess.fen(),
|
|
29
28
|
});
|
|
30
29
|
});
|
|
31
30
|
|
|
32
31
|
// finally, add the first chess "moment" when needed
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
32
|
+
const headers = chess.getHeaders();
|
|
33
|
+
if (headers.FEN || fen === initial || first.comment || first.shapes) {
|
|
35
34
|
moments.unshift(first);
|
|
36
35
|
}
|
|
37
36
|
|
|
@@ -20,21 +20,24 @@ const previousFen = (history, moves, currentDepth, previousDepth) => {
|
|
|
20
20
|
return history.get(previousDepth)[0];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const currentHistory = history.get(currentDepth);
|
|
24
|
+
if (Array.isArray(currentHistory) && currentHistory.length > 0) {
|
|
25
|
+
// try the first candidate from the current depth
|
|
26
|
+
const candidate = currentHistory[0];
|
|
27
|
+
if (matchesFen(moves, candidate)) {
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// try the second candidate from the current depth
|
|
32
|
+
const second = currentHistory[1];
|
|
33
|
+
if (matchesFen(moves, second)) {
|
|
34
|
+
return second;
|
|
35
|
+
}
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
return previousFen(history, moves, currentDepth - 1, previousDepth);
|
|
36
39
|
} catch (err) {
|
|
37
|
-
return
|
|
40
|
+
return '';
|
|
38
41
|
}
|
|
39
42
|
};
|
|
40
43
|
|
package/functions/tree.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
const makeMoments = require('./make-moments');
|
|
2
2
|
|
|
3
3
|
const tree = (sloppyPgn) => {
|
|
4
|
-
|
|
4
|
+
try {
|
|
5
|
+
const moments = makeMoments(sloppyPgn);
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
let index = 0;
|
|
8
|
+
for (const lines of moments) {
|
|
9
|
+
for (const moment of lines) {
|
|
10
|
+
moment.index = index;
|
|
11
|
+
index++;
|
|
12
|
+
}
|
|
11
13
|
}
|
|
12
|
-
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
return moments;
|
|
16
|
+
} catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
module.exports = tree;
|
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { fen, pgn, flat, tree, mainline } = require('./functions');
|
|
2
2
|
const {
|
|
3
|
+
addMomentToTree,
|
|
3
4
|
getNextMoments,
|
|
4
5
|
getPrevMoment,
|
|
5
6
|
momentsToPgn,
|
|
@@ -12,6 +13,7 @@ module.exports = {
|
|
|
12
13
|
flat,
|
|
13
14
|
tree,
|
|
14
15
|
mainline,
|
|
16
|
+
addMomentToTree,
|
|
15
17
|
getNextMoments,
|
|
16
18
|
getPrevMoment,
|
|
17
19
|
momentsToPgn,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chess-moments",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "PGN parser that transforms PGN files into chess \"moments\"",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"chess.js": "^
|
|
27
|
+
"chess.js": "^1.4.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"chai": "^4.3.7",
|
package/chess.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const isBrowser =
|
|
2
|
-
typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
3
|
-
const isNode = !isBrowser;
|
|
4
|
-
|
|
5
|
-
let Chess;
|
|
6
|
-
|
|
7
|
-
if (isNode) {
|
|
8
|
-
const chess = require('chess.js');
|
|
9
|
-
Chess = chess.Chess;
|
|
10
|
-
} else {
|
|
11
|
-
Chess = require('chess.js');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
module.exports = Chess;
|
|
15
|
-
module.exports.Chess = Chess;
|