shogiops 0.2.8 → 0.5.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/attacks.js +32 -48
- package/attacks.js.map +1 -1
- package/board.d.ts +3 -0
- package/board.js +60 -33
- package/board.js.map +1 -1
- package/compat.d.ts +6 -24
- package/compat.js +20 -185
- package/compat.js.map +1 -1
- package/debug.js +37 -72
- package/debug.js.map +1 -1
- package/hand.d.ts +38 -0
- package/hand.js +69 -0
- package/hand.js.map +1 -0
- package/hash.d.ts +4 -3
- package/hash.js +12 -20
- package/hash.js.map +1 -1
- package/index.d.ts +13 -10
- package/index.js +22 -64
- package/index.js.map +1 -1
- package/{csa.d.ts → notation/csa/csa.d.ts} +9 -9
- package/notation/csa/csa.js +213 -0
- package/notation/csa/csa.js.map +1 -0
- package/notation/japanese.d.ts +3 -0
- package/notation/japanese.js +61 -0
- package/notation/japanese.js.map +1 -0
- package/{kif.d.ts → notation/kif/kif.d.ts} +10 -9
- package/notation/kif/kif.js +232 -0
- package/notation/kif/kif.js.map +1 -0
- package/{kifHandicaps.d.ts → notation/kif/kifHandicaps.d.ts} +0 -0
- package/{kifHandicaps.js → notation/kif/kifHandicaps.js} +7 -7
- package/notation/kif/kifHandicaps.js.map +1 -0
- package/notation/kitaoKawasaki.d.ts +3 -0
- package/notation/kitaoKawasaki.js +26 -0
- package/notation/kitaoKawasaki.js.map +1 -0
- package/notation/kitaokawasaki.d.ts +0 -0
- package/notation/kitaokawasaki.js +2 -0
- package/notation/kitaokawasaki.js.map +1 -0
- package/notation/notationUtil.d.ts +12 -0
- package/{kifUtil.js → notation/notationUtil.js} +39 -40
- package/notation/notationUtil.js.map +1 -0
- package/notation/western.d.ts +3 -0
- package/notation/western.js +23 -0
- package/notation/western.js.map +1 -0
- package/notation/westernEngine.d.ts +3 -0
- package/notation/westernEngine.js +23 -0
- package/notation/westernEngine.js.map +1 -0
- package/package.json +54 -37
- package/setup.d.ts +2 -32
- package/setup.js +5 -80
- package/setup.js.map +1 -1
- package/sfen.d.ts +33 -0
- package/sfen.js +188 -0
- package/sfen.js.map +1 -0
- package/shogi.d.ts +14 -7
- package/shogi.js +138 -141
- package/shogi.js.map +1 -1
- package/squareSet.d.ts +1 -5
- package/squareSet.js +1 -18
- package/squareSet.js.map +1 -1
- package/src/board.ts +34 -0
- package/src/compat.ts +18 -197
- package/src/debug.ts +26 -56
- package/src/hand.ts +94 -0
- package/src/hash.ts +8 -7
- package/src/index.ts +16 -12
- package/src/{csa.ts → notation/csa/csa.ts} +53 -52
- package/src/notation/japanese.ts +74 -0
- package/src/{kif.ts → notation/kif/kif.ts} +77 -78
- package/src/{kifHandicaps.ts → notation/kif/kifHandicaps.ts} +5 -0
- package/src/notation/kitaoKawasaki.ts +24 -0
- package/src/{kifUtil.ts → notation/notationUtil.ts} +37 -28
- package/src/notation/western.ts +21 -0
- package/src/notation/westernEngine.ts +21 -0
- package/src/setup.ts +4 -91
- package/src/sfen.ts +190 -0
- package/src/shogi.ts +75 -75
- package/src/squareSet.ts +1 -18
- package/src/transform.ts +3 -1
- package/src/types.ts +13 -16
- package/src/util.ts +8 -73
- package/src/variant.ts +44 -3
- package/src/variantUtil.ts +160 -0
- package/transform.js +18 -24
- package/transform.js.map +1 -1
- package/types.d.ts +3 -7
- package/types.js +18 -24
- package/types.js.map +1 -1
- package/util.d.ts +4 -7
- package/util.js +24 -96
- package/util.js.map +1 -1
- package/variant.d.ts +12 -1
- package/variant.js +42 -16
- package/variant.js.map +1 -1
- package/variantUtil.d.ts +13 -0
- package/variantUtil.js +147 -0
- package/variantUtil.js.map +1 -0
- package/csa.js +0 -229
- package/csa.js.map +0 -1
- package/csaUtil.d.ts +0 -3
- package/csaUtil.js +0 -21
- package/csaUtil.js.map +0 -1
- package/fen.d.ts +0 -32
- package/fen.js +0 -197
- package/fen.js.map +0 -1
- package/kif.js +0 -247
- package/kif.js.map +0 -1
- package/kifHandicaps.js.map +0 -1
- package/kifUtil.d.ts +0 -9
- package/kifUtil.js.map +0 -1
- package/san.d.ts +0 -6
- package/san.js +0 -135
- package/san.js.map +0 -1
- package/src/csaUtil.ts +0 -15
- package/src/fen.ts +0 -183
- package/src/san.ts +0 -136
package/src/csaUtil.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Square } from './types';
|
|
2
|
-
import { squareFile, squareRank } from './util';
|
|
3
|
-
|
|
4
|
-
export function parseCsaSquare(str: string): Square | undefined {
|
|
5
|
-
if (str.length !== 2) return;
|
|
6
|
-
if (str === '00') return 0; // for pocket
|
|
7
|
-
const file = Math.abs(str.charCodeAt(0) - '9'.charCodeAt(0));
|
|
8
|
-
const rank = Math.abs(str.charCodeAt(1) - '9'.charCodeAt(0));
|
|
9
|
-
if (file < 0 || file >= 9 || rank < 0 || rank >= 9) return;
|
|
10
|
-
return file + 9 * rank;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function makeCsaSquare(sq: Square): string {
|
|
14
|
-
return (9 - squareFile(sq)).toString() + (9 - squareRank(sq));
|
|
15
|
-
}
|
package/src/fen.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { Result } from '@badrap/result';
|
|
2
|
-
import { Piece, Color, POCKET_ROLES, PocketRole } from './types';
|
|
3
|
-
import { Board } from './board';
|
|
4
|
-
import { Setup, MaterialSide, Material } from './setup';
|
|
5
|
-
import { defined, roleToChar, charToRole, toBW } from './util';
|
|
6
|
-
|
|
7
|
-
export const INITIAL_BOARD_FEN = 'lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL';
|
|
8
|
-
export const INITIAL_EPD = INITIAL_BOARD_FEN + ' b -';
|
|
9
|
-
export const INITIAL_FEN = INITIAL_EPD + ' 1';
|
|
10
|
-
export const EMPTY_BOARD_FEN = '9/9/9/9/9/9/9/9/9';
|
|
11
|
-
export const EMPTY_EPD = EMPTY_BOARD_FEN + ' b -';
|
|
12
|
-
export const EMPTY_FEN = EMPTY_EPD + ' 1';
|
|
13
|
-
|
|
14
|
-
export enum InvalidFen {
|
|
15
|
-
Fen = 'ERR_FEN',
|
|
16
|
-
Board = 'ERR_BOARD',
|
|
17
|
-
Pockets = 'ERR_POCKETS',
|
|
18
|
-
Turn = 'ERR_TURN',
|
|
19
|
-
Fullmoves = 'ERR_FULLMOVES',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class FenError extends Error {}
|
|
23
|
-
|
|
24
|
-
function parseSmallUint(str: string): number | undefined {
|
|
25
|
-
return /^\d{1,4}$/.test(str) ? parseInt(str, 10) : undefined;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function charToPiece(ch: string): Piece | undefined {
|
|
29
|
-
const role = charToRole(ch);
|
|
30
|
-
return role && { role, color: ch.toLowerCase() === ch ? 'gote' : 'sente' };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function parseBoardFen(boardPart: string): Result<Board, FenError> {
|
|
34
|
-
const board = Board.empty();
|
|
35
|
-
let rank = 8;
|
|
36
|
-
let file = 0;
|
|
37
|
-
for (let i = 0; i < boardPart.length; i++) {
|
|
38
|
-
let c = boardPart[i];
|
|
39
|
-
if (c === '/' && file === 9) {
|
|
40
|
-
file = 0;
|
|
41
|
-
rank--;
|
|
42
|
-
} else {
|
|
43
|
-
const step = parseInt(c, 10);
|
|
44
|
-
if (step > 0) file += step;
|
|
45
|
-
else {
|
|
46
|
-
if (file >= 9 || rank < 0) return Result.err(new FenError(InvalidFen.Board));
|
|
47
|
-
if (c === '+' && i + 1 < boardPart.length) c += boardPart[++i];
|
|
48
|
-
const square = file + rank * 9;
|
|
49
|
-
const piece = charToPiece(c);
|
|
50
|
-
if (!piece) return Result.err(new FenError(InvalidFen.Board));
|
|
51
|
-
board.set(square, piece);
|
|
52
|
-
file++;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (rank !== 0 || file !== 9) return Result.err(new FenError(InvalidFen.Board));
|
|
57
|
-
return Result.ok(board);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function parsePockets(pocketPart: string): Result<Material, FenError> {
|
|
61
|
-
if (pocketPart.toLowerCase().includes('k')) return Result.err(new FenError(InvalidFen.Pockets));
|
|
62
|
-
const pockets = Material.empty();
|
|
63
|
-
for (let i = 0; i < pocketPart.length; i++) {
|
|
64
|
-
if (pocketPart[i] === '-') break;
|
|
65
|
-
// max 99
|
|
66
|
-
let count: number;
|
|
67
|
-
if (parseInt(pocketPart[i]) >= 0) {
|
|
68
|
-
count = parseInt(pocketPart[i++], 10);
|
|
69
|
-
if (parseInt(pocketPart[i]) >= 0) count = count * 10 + parseInt(pocketPart[i++], 10);
|
|
70
|
-
} else count = 1;
|
|
71
|
-
const piece = charToPiece(pocketPart[i]);
|
|
72
|
-
if (!piece) return Result.err(new FenError(InvalidFen.Pockets));
|
|
73
|
-
pockets[piece.color][piece.role as PocketRole] += count;
|
|
74
|
-
}
|
|
75
|
-
return Result.ok(pockets);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function parseFen(fen: string): Result<Setup, FenError> {
|
|
79
|
-
const parts = fen.split(' ');
|
|
80
|
-
|
|
81
|
-
// Board
|
|
82
|
-
const boardPart = parts.shift()!;
|
|
83
|
-
const board: Result<Board, FenError> = parseBoardFen(boardPart);
|
|
84
|
-
|
|
85
|
-
// Turn
|
|
86
|
-
const turnPart = parts.shift();
|
|
87
|
-
let turn: Color;
|
|
88
|
-
if (!defined(turnPart) || turnPart === 'b') turn = 'sente';
|
|
89
|
-
else if (turnPart === 'w') turn = 'gote';
|
|
90
|
-
else return Result.err(new FenError(InvalidFen.Turn));
|
|
91
|
-
|
|
92
|
-
// Pocket
|
|
93
|
-
const pocketPart = parts.shift();
|
|
94
|
-
let pockets: Result<Material, FenError>;
|
|
95
|
-
if (!defined(pocketPart)) pockets = Result.ok(Material.empty());
|
|
96
|
-
else pockets = parsePockets(pocketPart);
|
|
97
|
-
|
|
98
|
-
// Turn
|
|
99
|
-
const fullmovesPart = parts.shift();
|
|
100
|
-
const fullmoves = defined(fullmovesPart) ? parseSmallUint(fullmovesPart) : 1;
|
|
101
|
-
if (!defined(fullmoves)) return Result.err(new FenError(InvalidFen.Fullmoves));
|
|
102
|
-
|
|
103
|
-
if (parts.length > 0) return Result.err(new FenError(InvalidFen.Fen));
|
|
104
|
-
|
|
105
|
-
return board.chain(board =>
|
|
106
|
-
pockets.map(pockets => {
|
|
107
|
-
return {
|
|
108
|
-
board,
|
|
109
|
-
pockets,
|
|
110
|
-
turn,
|
|
111
|
-
fullmoves: Math.max(1, fullmoves),
|
|
112
|
-
};
|
|
113
|
-
})
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
interface FenOpts {
|
|
118
|
-
epd?: boolean;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function parsePiece(str: string): Piece | undefined {
|
|
122
|
-
if (!str) return;
|
|
123
|
-
const piece = charToPiece(str[0]);
|
|
124
|
-
if (!piece) return;
|
|
125
|
-
else if (str.length > 1 && str[1] !== '+') return;
|
|
126
|
-
return piece;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function makePiece(piece: Piece): string {
|
|
130
|
-
let r = roleToChar(piece.role);
|
|
131
|
-
if (piece.color === 'sente') r = r.toUpperCase();
|
|
132
|
-
return r;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function makeBoardFen(board: Board): string {
|
|
136
|
-
let fen = '';
|
|
137
|
-
let empty = 0;
|
|
138
|
-
for (let rank = 8; rank >= 0; rank--) {
|
|
139
|
-
for (let file = 0; file < 9; file++) {
|
|
140
|
-
const square = file + rank * 9;
|
|
141
|
-
const piece = board.get(square);
|
|
142
|
-
if (!piece) empty++;
|
|
143
|
-
else {
|
|
144
|
-
if (empty > 0) {
|
|
145
|
-
fen += empty;
|
|
146
|
-
empty = 0;
|
|
147
|
-
}
|
|
148
|
-
fen += makePiece(piece);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (file === 8) {
|
|
152
|
-
if (empty > 0) {
|
|
153
|
-
fen += empty;
|
|
154
|
-
empty = 0;
|
|
155
|
-
}
|
|
156
|
-
if (rank !== 0) fen += '/';
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return fen;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function makePocket(material: MaterialSide): string {
|
|
164
|
-
return POCKET_ROLES.map(role => {
|
|
165
|
-
const r = roleToChar(role);
|
|
166
|
-
const n = material[role];
|
|
167
|
-
return n > 1 ? n + r : n === 1 ? r : '';
|
|
168
|
-
}).join('');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function makePockets(pocket: Material): string {
|
|
172
|
-
const pockets = makePocket(pocket.sente).toUpperCase() + makePocket(pocket.gote);
|
|
173
|
-
return pockets === '' ? '-' : pockets;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function makeFen(setup: Setup, opts?: FenOpts): string {
|
|
177
|
-
return [
|
|
178
|
-
makeBoardFen(setup.board),
|
|
179
|
-
toBW(setup.turn),
|
|
180
|
-
makePockets(setup.pockets),
|
|
181
|
-
...(opts?.epd ? [] : [Math.max(1, Math.min(setup.fullmoves, 9999))]),
|
|
182
|
-
].join(' ');
|
|
183
|
-
}
|
package/src/san.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { isDrop, Move, PocketRole, PROMOTABLE_ROLES } from './types';
|
|
2
|
-
import { defined, makeSquare } from './util';
|
|
3
|
-
import { SquareSet } from './squareSet';
|
|
4
|
-
import { Position } from './shogi';
|
|
5
|
-
import { lishogiCharToRole, roleToLishogiChar, shogiCoordToChessCord, parseChessSquare } from './compat';
|
|
6
|
-
|
|
7
|
-
function makeSanWithoutSuffix(pos: Position, move: Move): string {
|
|
8
|
-
let san = '';
|
|
9
|
-
if (isDrop(move)) {
|
|
10
|
-
san = roleToLishogiChar(move.role).toUpperCase() + '*' + shogiCoordToChessCord(makeSquare(move.to));
|
|
11
|
-
} else {
|
|
12
|
-
const role = pos.board.getRole(move.from);
|
|
13
|
-
if (!role) return '--';
|
|
14
|
-
const capture = pos.board.occupied.has(move.to);
|
|
15
|
-
san = roleToLishogiChar(role).toUpperCase();
|
|
16
|
-
|
|
17
|
-
if (role !== 'pawn' && role !== 'lance' && role !== 'king') {
|
|
18
|
-
// Disambiguation
|
|
19
|
-
let others = SquareSet.empty();
|
|
20
|
-
for (const s of pos.board.pieces(pos.turn, role)) {
|
|
21
|
-
if (pos.dests(s).has(move.to)) others = others.union(SquareSet.fromSquare(s));
|
|
22
|
-
}
|
|
23
|
-
others = others.without(move.from);
|
|
24
|
-
if (others.nonEmpty()) {
|
|
25
|
-
san += shogiCoordToChessCord(makeSquare(move.from));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
if (capture) san += 'x';
|
|
29
|
-
san += shogiCoordToChessCord(makeSquare(move.to));
|
|
30
|
-
if (move.promotion) san += '+';
|
|
31
|
-
// Do we need to force promotion?
|
|
32
|
-
else if (
|
|
33
|
-
(role === 'knight' && SquareSet.backrank2(pos.turn).has(move.to)) ||
|
|
34
|
-
((role === 'pawn' || role === 'lance') && SquareSet.backrank(pos.turn).has(move.to))
|
|
35
|
-
)
|
|
36
|
-
san += '+';
|
|
37
|
-
else if (
|
|
38
|
-
(PROMOTABLE_ROLES as ReadonlyArray<string>).includes(role) &&
|
|
39
|
-
SquareSet.promotionZone(pos.turn).has(move.to)
|
|
40
|
-
)
|
|
41
|
-
san += '=';
|
|
42
|
-
}
|
|
43
|
-
return san;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function makeSanAndPlay(pos: Position, move: Move): string {
|
|
47
|
-
const san = makeSanWithoutSuffix(pos, move);
|
|
48
|
-
pos.play(move);
|
|
49
|
-
return san;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function makeSanVariation(pos: Position, variation: Move[]): string {
|
|
53
|
-
pos = pos.clone();
|
|
54
|
-
const line = [];
|
|
55
|
-
for (let i = 0; i < variation.length; i++) {
|
|
56
|
-
if (i !== 0) line.push(' ');
|
|
57
|
-
line.push(pos.fullmoves, '. ');
|
|
58
|
-
const san = makeSanWithoutSuffix(pos, variation[i]);
|
|
59
|
-
pos.play(variation[i]);
|
|
60
|
-
line.push(san);
|
|
61
|
-
if (san === '--') return line.join('');
|
|
62
|
-
if (i === variation.length - 1 && pos.outcome()?.winner) line.push('投了');
|
|
63
|
-
}
|
|
64
|
-
return line.join('');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function makeSan(pos: Position, move: Move): string {
|
|
68
|
-
return makeSanAndPlay(pos.clone(), move);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function parseSan(pos: Position, san: string): Move | undefined {
|
|
72
|
-
const ctx = pos.ctx();
|
|
73
|
-
|
|
74
|
-
// Normal move
|
|
75
|
-
const match = san.match(/^([PLNSGKBRTUMAHD])([a-i][1-9])?[x]?([a-i][1-9])(\+|\=)?$/);
|
|
76
|
-
if (!match) {
|
|
77
|
-
// Drop
|
|
78
|
-
const match = san.match(/^([PLNSGBRplsgbr])\*([a-i][1-9])$/);
|
|
79
|
-
if (!match) return;
|
|
80
|
-
const move = {
|
|
81
|
-
role: lishogiCharToRole(match[1]) as PocketRole,
|
|
82
|
-
to: parseChessSquare(match[2])!,
|
|
83
|
-
};
|
|
84
|
-
return pos.isLegal(move, ctx) ? move : undefined;
|
|
85
|
-
}
|
|
86
|
-
const role = lishogiCharToRole(match[1])!;
|
|
87
|
-
const to = parseChessSquare(match[3])!;
|
|
88
|
-
const fromStr = match[2];
|
|
89
|
-
|
|
90
|
-
let candidates = pos.board.pieces(pos.turn, role);
|
|
91
|
-
if (fromStr) candidates = candidates.intersect(SquareSet.fromSquare(parseChessSquare(fromStr)!));
|
|
92
|
-
|
|
93
|
-
// Check uniqueness and legality
|
|
94
|
-
let from;
|
|
95
|
-
for (const candidate of candidates) {
|
|
96
|
-
if (pos.dests(candidate, ctx).has(to)) {
|
|
97
|
-
if (defined(from)) return; // Ambiguous
|
|
98
|
-
from = candidate;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (!defined(from)) return; // Illegal
|
|
102
|
-
|
|
103
|
-
const promotionStr = match[4];
|
|
104
|
-
let promotion: boolean;
|
|
105
|
-
if (promotionStr === '+') promotion = true;
|
|
106
|
-
else promotion = false;
|
|
107
|
-
|
|
108
|
-
// Promotion needs to be specified in san
|
|
109
|
-
if (
|
|
110
|
-
!defined(promotionStr) &&
|
|
111
|
-
(PROMOTABLE_ROLES as ReadonlyArray<string>).includes(role) &&
|
|
112
|
-
(SquareSet.promotionZone(pos.turn).has(to) || SquareSet.promotionZone(pos.turn).has(from))
|
|
113
|
-
)
|
|
114
|
-
return;
|
|
115
|
-
|
|
116
|
-
// role can't be promoted/unpromoted or it isn't in/from the promotion zone
|
|
117
|
-
if (
|
|
118
|
-
promotionStr &&
|
|
119
|
-
(!(PROMOTABLE_ROLES as ReadonlyArray<string>).includes(role) ||
|
|
120
|
-
(!SquareSet.promotionZone(pos.turn).has(to) && !SquareSet.promotionZone(pos.turn).has(from)))
|
|
121
|
-
)
|
|
122
|
-
return;
|
|
123
|
-
// force promotion
|
|
124
|
-
else if (
|
|
125
|
-
!promotion &&
|
|
126
|
-
(((role === 'pawn' || role === 'lance') && SquareSet.backrank(pos.turn).has(to)) ||
|
|
127
|
-
(role === 'knight' && SquareSet.backrank2(pos.turn).has(to)))
|
|
128
|
-
)
|
|
129
|
-
return;
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
from,
|
|
133
|
-
to,
|
|
134
|
-
promotion,
|
|
135
|
-
};
|
|
136
|
-
}
|