shogiops 0.2.7 → 0.4.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/LICENSE.txt +0 -0
- package/README.md +0 -0
- package/attacks.d.ts +0 -0
- package/attacks.js +7 -7
- package/attacks.js.map +1 -1
- package/board.d.ts +3 -0
- package/board.js +31 -0
- package/board.js.map +1 -1
- package/compat.d.ts +6 -24
- package/compat.js +15 -171
- package/compat.js.map +1 -1
- package/csa.d.ts +4 -3
- package/csa.js +38 -33
- package/csa.js.map +1 -1
- package/csaUtil.d.ts +1 -1
- package/csaUtil.js +3 -3
- package/csaUtil.js.map +1 -1
- package/debug.d.ts +0 -0
- package/debug.js +29 -55
- package/debug.js.map +1 -1
- package/fen.d.ts +6 -5
- package/fen.js +53 -49
- package/fen.js.map +1 -1
- package/hand.d.ts +38 -0
- package/hand.js +74 -0
- package/hand.js.map +1 -0
- package/hash.d.ts +4 -3
- package/hash.js +9 -9
- package/hash.js.map +1 -1
- package/index.d.ts +4 -3
- package/index.js +3 -7
- package/index.js.map +1 -1
- package/kif.d.ts +5 -4
- package/kif.js +70 -65
- package/kif.js.map +1 -1
- package/kifHandicaps.d.ts +0 -0
- package/kifHandicaps.js +5 -0
- package/kifHandicaps.js.map +1 -1
- package/kifUtil.d.ts +0 -0
- package/kifUtil.js +4 -4
- package/kifUtil.js.map +1 -1
- package/package.json +9 -5
- package/setup.d.ts +2 -32
- package/setup.js +3 -74
- package/setup.js.map +1 -1
- package/shogi.d.ts +14 -7
- package/shogi.js +109 -106
- package/shogi.js.map +1 -1
- package/squareSet.d.ts +1 -5
- package/squareSet.js +0 -13
- package/squareSet.js.map +1 -1
- package/src/attacks.ts +0 -0
- package/src/board.ts +34 -0
- package/src/compat.ts +18 -197
- package/src/csa.ts +29 -21
- package/src/csaUtil.ts +3 -2
- package/src/debug.ts +25 -55
- package/src/fen.ts +42 -35
- package/src/hand.ts +94 -0
- package/src/hash.ts +8 -7
- package/src/index.ts +5 -5
- package/src/kif.ts +51 -43
- package/src/kifHandicaps.ts +5 -0
- package/src/kifUtil.ts +0 -0
- package/src/setup.ts +4 -91
- 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 +2 -68
- package/src/variant.ts +44 -3
- package/src/variantUtil.ts +160 -0
- package/transform.d.ts +0 -0
- package/transform.js +3 -1
- package/transform.js.map +1 -1
- package/types.d.ts +3 -7
- package/types.js +13 -14
- package/types.js.map +1 -1
- package/util.d.ts +1 -4
- package/util.js +2 -54
- package/util.js.map +1 -1
- package/variant.d.ts +12 -1
- package/variant.js +38 -3
- package/variant.js.map +1 -1
- package/variantUtil.d.ts +13 -0
- package/variantUtil.js +160 -0
- package/variantUtil.js.map +1 -0
- package/san.d.ts +0 -6
- package/san.js +0 -135
- package/san.js.map +0 -1
- package/src/san.ts +0 -136
package/src/fen.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Result } from '@badrap/result';
|
|
2
|
-
import { Piece, Color
|
|
2
|
+
import { Piece, Color } from './types';
|
|
3
3
|
import { Board } from './board';
|
|
4
|
-
import { Setup
|
|
4
|
+
import { Setup } from './setup';
|
|
5
5
|
import { defined, roleToChar, charToRole, toBW } from './util';
|
|
6
|
+
import { Hand, Hands } from './hand';
|
|
7
|
+
import { ROLES } from './types';
|
|
6
8
|
|
|
7
9
|
export const INITIAL_BOARD_FEN = 'lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL';
|
|
8
10
|
export const INITIAL_EPD = INITIAL_BOARD_FEN + ' b -';
|
|
@@ -14,7 +16,7 @@ export const EMPTY_FEN = EMPTY_EPD + ' 1';
|
|
|
14
16
|
export enum InvalidFen {
|
|
15
17
|
Fen = 'ERR_FEN',
|
|
16
18
|
Board = 'ERR_BOARD',
|
|
17
|
-
|
|
19
|
+
Hands = 'ERR_HANDS',
|
|
18
20
|
Turn = 'ERR_TURN',
|
|
19
21
|
Fullmoves = 'ERR_FULLMOVES',
|
|
20
22
|
}
|
|
@@ -32,12 +34,18 @@ function charToPiece(ch: string): Piece | undefined {
|
|
|
32
34
|
|
|
33
35
|
export function parseBoardFen(boardPart: string): Result<Board, FenError> {
|
|
34
36
|
const board = Board.empty();
|
|
37
|
+
const ranks = boardPart.split('/');
|
|
38
|
+
board.numberOfRanks = ranks.length;
|
|
39
|
+
// we assume the board is square
|
|
40
|
+
// since that's good enough for now...
|
|
41
|
+
board.numberOfFiles = board.numberOfRanks;
|
|
42
|
+
const offset = 9 - board.numberOfFiles;
|
|
35
43
|
let rank = 8;
|
|
36
|
-
let file =
|
|
44
|
+
let file = offset;
|
|
37
45
|
for (let i = 0; i < boardPart.length; i++) {
|
|
38
46
|
let c = boardPart[i];
|
|
39
47
|
if (c === '/' && file === 9) {
|
|
40
|
-
file =
|
|
48
|
+
file = offset;
|
|
41
49
|
rank--;
|
|
42
50
|
} else {
|
|
43
51
|
const step = parseInt(c, 10);
|
|
@@ -53,26 +61,25 @@ export function parseBoardFen(boardPart: string): Result<Board, FenError> {
|
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
|
-
if (rank !==
|
|
64
|
+
if (rank !== offset || file !== 9) return Result.err(new FenError(InvalidFen.Board));
|
|
57
65
|
return Result.ok(board);
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
export function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (pocketPart[i] === '-') break;
|
|
68
|
+
export function parseHands(handsPart: string): Result<Hands, FenError> {
|
|
69
|
+
const hands = Hands.empty();
|
|
70
|
+
for (let i = 0; i < handsPart.length; i++) {
|
|
71
|
+
if (handsPart[i] === '-') break;
|
|
65
72
|
// max 99
|
|
66
73
|
let count: number;
|
|
67
|
-
if (parseInt(
|
|
68
|
-
count = parseInt(
|
|
69
|
-
if (parseInt(
|
|
74
|
+
if (parseInt(handsPart[i]) >= 0) {
|
|
75
|
+
count = parseInt(handsPart[i++], 10);
|
|
76
|
+
if (parseInt(handsPart[i]) >= 0) count = count * 10 + parseInt(handsPart[i++], 10);
|
|
70
77
|
} else count = 1;
|
|
71
|
-
const piece = charToPiece(
|
|
72
|
-
if (!piece) return Result.err(new FenError(InvalidFen.
|
|
73
|
-
|
|
78
|
+
const piece = charToPiece(handsPart[i]);
|
|
79
|
+
if (!piece) return Result.err(new FenError(InvalidFen.Hands));
|
|
80
|
+
hands[piece.color][piece.role] += count;
|
|
74
81
|
}
|
|
75
|
-
return Result.ok(
|
|
82
|
+
return Result.ok(hands);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
export function parseFen(fen: string): Result<Setup, FenError> {
|
|
@@ -89,11 +96,11 @@ export function parseFen(fen: string): Result<Setup, FenError> {
|
|
|
89
96
|
else if (turnPart === 'w') turn = 'gote';
|
|
90
97
|
else return Result.err(new FenError(InvalidFen.Turn));
|
|
91
98
|
|
|
92
|
-
//
|
|
93
|
-
const
|
|
94
|
-
let
|
|
95
|
-
if (!defined(
|
|
96
|
-
else
|
|
99
|
+
// Hands
|
|
100
|
+
const handsPart = parts.shift();
|
|
101
|
+
let hands: Result<Hands, FenError>;
|
|
102
|
+
if (!defined(handsPart)) hands = Result.ok(Hands.empty());
|
|
103
|
+
else hands = parseHands(handsPart);
|
|
97
104
|
|
|
98
105
|
// Turn
|
|
99
106
|
const fullmovesPart = parts.shift();
|
|
@@ -103,10 +110,10 @@ export function parseFen(fen: string): Result<Setup, FenError> {
|
|
|
103
110
|
if (parts.length > 0) return Result.err(new FenError(InvalidFen.Fen));
|
|
104
111
|
|
|
105
112
|
return board.chain(board =>
|
|
106
|
-
|
|
113
|
+
hands.map(hands => {
|
|
107
114
|
return {
|
|
108
115
|
board,
|
|
109
|
-
|
|
116
|
+
hands,
|
|
110
117
|
turn,
|
|
111
118
|
fullmoves: Math.max(1, fullmoves),
|
|
112
119
|
};
|
|
@@ -135,8 +142,8 @@ export function makePiece(piece: Piece): string {
|
|
|
135
142
|
export function makeBoardFen(board: Board): string {
|
|
136
143
|
let fen = '';
|
|
137
144
|
let empty = 0;
|
|
138
|
-
for (let rank = 8; rank >=
|
|
139
|
-
for (let file =
|
|
145
|
+
for (let rank = 8; rank >= 9 - board.numberOfRanks; rank--) {
|
|
146
|
+
for (let file = 9 - board.numberOfFiles; file < 9; file++) {
|
|
140
147
|
const square = file + rank * 9;
|
|
141
148
|
const piece = board.get(square);
|
|
142
149
|
if (!piece) empty++;
|
|
@@ -153,31 +160,31 @@ export function makeBoardFen(board: Board): string {
|
|
|
153
160
|
fen += empty;
|
|
154
161
|
empty = 0;
|
|
155
162
|
}
|
|
156
|
-
if (rank !==
|
|
163
|
+
if (rank !== 9 - board.numberOfRanks) fen += '/';
|
|
157
164
|
}
|
|
158
165
|
}
|
|
159
166
|
}
|
|
160
167
|
return fen;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
|
-
export function
|
|
164
|
-
return
|
|
170
|
+
export function makeHand(hand: Hand): string {
|
|
171
|
+
return ROLES.map(role => {
|
|
165
172
|
const r = roleToChar(role);
|
|
166
|
-
const n =
|
|
173
|
+
const n = hand[role];
|
|
167
174
|
return n > 1 ? n + r : n === 1 ? r : '';
|
|
168
175
|
}).join('');
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
export function
|
|
172
|
-
const
|
|
173
|
-
return
|
|
178
|
+
export function makeHands(hands: Hands): string {
|
|
179
|
+
const handsStr = makeHand(hands.sente).toUpperCase() + makeHand(hands.gote);
|
|
180
|
+
return handsStr === '' ? '-' : handsStr;
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
export function makeFen(setup: Setup, opts?: FenOpts): string {
|
|
177
184
|
return [
|
|
178
185
|
makeBoardFen(setup.board),
|
|
179
186
|
toBW(setup.turn),
|
|
180
|
-
|
|
187
|
+
makeHands(setup.hands),
|
|
181
188
|
...(opts?.epd ? [] : [Math.max(1, Math.min(setup.fullmoves, 9999))]),
|
|
182
189
|
].join(' ');
|
|
183
190
|
}
|
package/src/hand.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Role, ROLES } from './types';
|
|
2
|
+
|
|
3
|
+
// Hand alone can store anything
|
|
4
|
+
// let the variants decide what to store and what not
|
|
5
|
+
export class Hand {
|
|
6
|
+
pawn: number;
|
|
7
|
+
lance: number;
|
|
8
|
+
knight: number;
|
|
9
|
+
silver: number;
|
|
10
|
+
gold: number;
|
|
11
|
+
bishop: number;
|
|
12
|
+
rook: number;
|
|
13
|
+
tokin: number;
|
|
14
|
+
promotedlance: number;
|
|
15
|
+
promotedknight: number;
|
|
16
|
+
promotedsilver: number;
|
|
17
|
+
horse: number;
|
|
18
|
+
dragon: number;
|
|
19
|
+
king: number;
|
|
20
|
+
|
|
21
|
+
private constructor() {}
|
|
22
|
+
|
|
23
|
+
static empty(): Hand {
|
|
24
|
+
const m = new Hand();
|
|
25
|
+
for (const role of ROLES) m[role] = 0;
|
|
26
|
+
return m;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
clone(): Hand {
|
|
30
|
+
const m = new Hand();
|
|
31
|
+
for (const role of ROLES) m[role] = this[role];
|
|
32
|
+
return m;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
equals(other: Hand): boolean {
|
|
36
|
+
return ROLES.every(role => this[role] === other[role]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
add(other: Hand): Hand {
|
|
40
|
+
const m = new Hand();
|
|
41
|
+
for (const role of ROLES) m[role] = this[role] + other[role];
|
|
42
|
+
return m;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
nonEmpty(): boolean {
|
|
46
|
+
return ROLES.some(role => this[role] > 0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
isEmpty(): boolean {
|
|
50
|
+
return !this.nonEmpty();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
count(): number {
|
|
54
|
+
return ROLES.map(role => this[role]).reduce((acc, cur) => acc + cur);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
*[Symbol.iterator](): Iterator<[Role, number]> {
|
|
58
|
+
for (const role of ROLES.filter(r => this[r] > 0)) {
|
|
59
|
+
yield [role, this[role]];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class Hands {
|
|
65
|
+
constructor(public gote: Hand, public sente: Hand) {}
|
|
66
|
+
|
|
67
|
+
static empty(): Hands {
|
|
68
|
+
return new Hands(Hand.empty(), Hand.empty());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
clone(): Hands {
|
|
72
|
+
return new Hands(this.gote.clone(), this.sente.clone());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
equals(other: Hands): boolean {
|
|
76
|
+
return this.gote.equals(other.gote) && this.sente.equals(other.sente);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
add(other: Hands): Hands {
|
|
80
|
+
return new Hands(this.gote.add(other.gote), this.sente.add(other.sente));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
count(): number {
|
|
84
|
+
return this.gote.count() + this.sente.count();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
isEmpty(): boolean {
|
|
88
|
+
return this.gote.isEmpty() && this.sente.isEmpty();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
nonEmpty(): boolean {
|
|
92
|
+
return !this.isEmpty();
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/hash.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { COLORS,
|
|
1
|
+
import { COLORS, ROLES } from './types';
|
|
2
2
|
import { Board } from './board';
|
|
3
|
-
import { Setup
|
|
3
|
+
import { Setup } from './setup';
|
|
4
|
+
import { Hand, Hands } from './hand';
|
|
4
5
|
|
|
5
6
|
function rol32(n: number, left: number): number {
|
|
6
7
|
return (n << left) | (n >>> (32 - left));
|
|
@@ -17,19 +18,19 @@ export function hashBoard(board: Board, state = 0): number {
|
|
|
17
18
|
return state;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export function
|
|
21
|
-
for (const role of
|
|
21
|
+
export function hashHand(hand: Hand, state = 0): number {
|
|
22
|
+
for (const role of ROLES) state = fxhash32(hand[role], state);
|
|
22
23
|
return state;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
export function
|
|
26
|
-
for (const color of COLORS) state =
|
|
26
|
+
export function hashHands(hands: Hands, state = 0): number {
|
|
27
|
+
for (const color of COLORS) state = hashHand(hands[color], state);
|
|
27
28
|
return state;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export function hashSetup(setup: Setup, state = 0): number {
|
|
31
32
|
state = hashBoard(setup.board, state);
|
|
32
|
-
state =
|
|
33
|
+
state = hashHands(setup.hands, state);
|
|
33
34
|
if (setup.turn === 'sente') state = fxhash32(1, state);
|
|
34
35
|
return state;
|
|
35
36
|
}
|
package/src/index.ts
CHANGED
|
@@ -11,8 +11,6 @@ export {
|
|
|
11
11
|
ByColor,
|
|
12
12
|
Role,
|
|
13
13
|
ROLES,
|
|
14
|
-
POCKET_ROLES,
|
|
15
|
-
PROMOTABLE_ROLES,
|
|
16
14
|
ByRole,
|
|
17
15
|
Piece,
|
|
18
16
|
NormalMove,
|
|
@@ -58,7 +56,7 @@ export {
|
|
|
58
56
|
|
|
59
57
|
export { Board } from './board';
|
|
60
58
|
|
|
61
|
-
export {
|
|
59
|
+
export { Setup, defaultSetup } from './setup';
|
|
62
60
|
|
|
63
61
|
export { IllegalSetup, Shogi, Position, PositionError, Context } from './shogi';
|
|
64
62
|
|
|
@@ -70,6 +68,8 @@ export * as fen from './fen';
|
|
|
70
68
|
|
|
71
69
|
export * as handicaps from './kifHandicaps';
|
|
72
70
|
|
|
71
|
+
export * as hand from './hand';
|
|
72
|
+
|
|
73
73
|
export * as hash from './hash';
|
|
74
74
|
|
|
75
75
|
export * as kif from './kif';
|
|
@@ -80,8 +80,8 @@ export * as csa from './csa';
|
|
|
80
80
|
|
|
81
81
|
export * as csaUtil from './csaUtil';
|
|
82
82
|
|
|
83
|
-
export * as san from './san';
|
|
84
|
-
|
|
85
83
|
export * as transform from './transform';
|
|
86
84
|
|
|
87
85
|
export * as variant from './variant';
|
|
86
|
+
|
|
87
|
+
export * as variantUtil from './variantUtil';
|
package/src/kif.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { Result } from '@badrap/result';
|
|
|
2
2
|
import { Board } from './board';
|
|
3
3
|
import { INITIAL_FEN, makeFen, parseFen } from './fen';
|
|
4
4
|
import { handicapNameToSfen, sfenToHandicapName } from './kifHandicaps';
|
|
5
|
-
import {
|
|
5
|
+
import { Setup } from './setup';
|
|
6
6
|
import { Position } from './shogi';
|
|
7
|
-
import { Color, isDrop, Move,
|
|
8
|
-
import { defined, kanjiToRole,
|
|
7
|
+
import { Color, isDrop, Move, Square } from './types';
|
|
8
|
+
import { defined, kanjiToRole, roleTo1Kanji, roleTo2Kanji } from './util';
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
kanjiToNumber,
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
numberToKanji,
|
|
16
16
|
parseKifSquare,
|
|
17
17
|
} from './kifUtil';
|
|
18
|
+
import { Hand, Hands } from './hand';
|
|
19
|
+
import { allRoles, handRoles, promote } from './variantUtil';
|
|
18
20
|
|
|
19
21
|
//
|
|
20
22
|
// KIF HEADER
|
|
@@ -24,7 +26,7 @@ export enum InvalidKif {
|
|
|
24
26
|
Kif = 'ERR_KIF',
|
|
25
27
|
Board = 'ERR_BOARD',
|
|
26
28
|
Handicap = 'ERR_HANDICAP',
|
|
27
|
-
|
|
29
|
+
Hands = 'ERR_HANDS',
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export class KifError extends Error {}
|
|
@@ -38,20 +40,23 @@ export function makeKifHeader(setup: Setup): string {
|
|
|
38
40
|
|
|
39
41
|
export function makeKifPositionHeader(setup: Setup): string {
|
|
40
42
|
return [
|
|
41
|
-
'後手の持駒:' +
|
|
43
|
+
'後手の持駒:' + makeKifHand(setup.hands.gote),
|
|
42
44
|
makeKifBoard(setup.board),
|
|
43
|
-
'先手の持駒:' +
|
|
45
|
+
'先手の持駒:' + makeKifHand(setup.hands.sente),
|
|
44
46
|
...(setup.turn === 'gote' ? ['後手番'] : []),
|
|
45
47
|
].join('\n');
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export function makeKifBoard(board: Board): string {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const kifFiles = ' 9 8 7 6 5 4 3 2 1'.slice(-(board.numberOfFiles * 2));
|
|
52
|
+
const separator = '+' + '-'.repeat(board.numberOfFiles * 3) + '+';
|
|
53
|
+
const offset = 9 - board.numberOfFiles;
|
|
54
|
+
let kifBoard = ' ' + kifFiles + `\n${separator}\n`;
|
|
55
|
+
for (let rank = 8; rank >= 9 - board.numberOfRanks; rank--) {
|
|
56
|
+
for (let file = offset; file < 9; file++) {
|
|
52
57
|
const square = file + rank * 9;
|
|
53
58
|
const piece = board.get(square);
|
|
54
|
-
if (file ===
|
|
59
|
+
if (file === offset) {
|
|
55
60
|
kifBoard += '|';
|
|
56
61
|
}
|
|
57
62
|
if (!piece) kifBoard += ' ・';
|
|
@@ -64,17 +69,18 @@ export function makeKifBoard(board: Board): string {
|
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
|
-
kifBoard +=
|
|
72
|
+
kifBoard += separator;
|
|
68
73
|
return kifBoard;
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
export function
|
|
72
|
-
if (
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
export function makeKifHand(hand: Hand): string {
|
|
77
|
+
if (hand.isEmpty()) return 'なし';
|
|
78
|
+
return handRoles('shogi')
|
|
79
|
+
.map(role => {
|
|
80
|
+
const r = roleTo1Kanji(role);
|
|
81
|
+
const n = hand[role];
|
|
82
|
+
return n > 1 ? r + numberToKanji(n) : n === 1 ? r : '';
|
|
83
|
+
})
|
|
78
84
|
.filter(p => p.length > 0)
|
|
79
85
|
.join(' ');
|
|
80
86
|
}
|
|
@@ -96,25 +102,21 @@ export function parseKifHeader(kif: string): Result<Setup, KifError> {
|
|
|
96
102
|
export function parseKifPositionHeader(kif: string): Result<Setup, KifError> {
|
|
97
103
|
const lines = normalizedKifLines(kif);
|
|
98
104
|
|
|
99
|
-
const
|
|
100
|
-
const
|
|
105
|
+
const goteHandStr = lines.find(l => l.startsWith('後手の持駒:'));
|
|
106
|
+
const senteHandStr = lines.find(l => l.startsWith('先手の持駒:'));
|
|
101
107
|
const turn = lines.some(l => l.startsWith('後手番')) ? 'gote' : 'sente';
|
|
102
108
|
|
|
103
109
|
const board: Result<Board, KifError> = parseKifBoard(kif);
|
|
104
110
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
: Result.ok(MaterialSide.empty());
|
|
108
|
-
const sentePocket = defined(sentePocketStr)
|
|
109
|
-
? parseKifHand(sentePocketStr.split(':')[1])
|
|
110
|
-
: Result.ok(MaterialSide.empty());
|
|
111
|
+
const goteHand = defined(goteHandStr) ? parseKifHand(goteHandStr.split(':')[1]) : Result.ok(Hand.empty());
|
|
112
|
+
const senteHand = defined(senteHandStr) ? parseKifHand(senteHandStr.split(':')[1]) : Result.ok(Hand.empty());
|
|
111
113
|
|
|
112
114
|
return board.chain(board =>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
goteHand.chain(gHand =>
|
|
116
|
+
senteHand.map(sHand => {
|
|
115
117
|
return {
|
|
116
118
|
board,
|
|
117
|
-
|
|
119
|
+
hands: new Hands(gHand, sHand),
|
|
118
120
|
turn,
|
|
119
121
|
fullmoves: 1,
|
|
120
122
|
};
|
|
@@ -125,13 +127,19 @@ export function parseKifPositionHeader(kif: string): Result<Setup, KifError> {
|
|
|
125
127
|
|
|
126
128
|
export function parseKifBoard(kifBoard: string): Result<Board, KifError> {
|
|
127
129
|
const lines = normalizedKifLines(kifBoard).filter(l => l.startsWith('|'));
|
|
128
|
-
if (lines.length
|
|
130
|
+
if (lines.length === 0) return Result.err(new KifError(InvalidKif.Board));
|
|
129
131
|
const board = Board.empty();
|
|
130
|
-
|
|
132
|
+
|
|
133
|
+
// assuming square board
|
|
134
|
+
board.numberOfRanks = lines.length;
|
|
135
|
+
board.numberOfFiles = lines.length;
|
|
136
|
+
|
|
137
|
+
const offset = 9 - lines.length;
|
|
138
|
+
let file = offset;
|
|
131
139
|
let rank = 9;
|
|
132
140
|
|
|
133
141
|
for (const l of lines) {
|
|
134
|
-
file =
|
|
142
|
+
file = offset;
|
|
135
143
|
rank--;
|
|
136
144
|
let gote = false;
|
|
137
145
|
let prom = false;
|
|
@@ -149,9 +157,9 @@ export function parseKifBoard(kifBoard: string): Result<Board, KifError> {
|
|
|
149
157
|
default:
|
|
150
158
|
if (file > 9 || rank < 0) return Result.err(new KifError(InvalidKif.Board));
|
|
151
159
|
const role = kanjiToRole(c);
|
|
152
|
-
if (defined(role)) {
|
|
160
|
+
if (defined(role) && allRoles('shogi').includes(role)) {
|
|
153
161
|
const square = file + rank * 9;
|
|
154
|
-
const piece = { role: prom ? promote(role) : role, color: (gote ? 'gote' : 'sente') as Color };
|
|
162
|
+
const piece = { role: prom ? promote('shogi')(role) : role, color: (gote ? 'gote' : 'sente') as Color };
|
|
155
163
|
board.set(square, piece);
|
|
156
164
|
prom = false;
|
|
157
165
|
gote = false;
|
|
@@ -160,27 +168,27 @@ export function parseKifBoard(kifBoard: string): Result<Board, KifError> {
|
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
|
-
if (rank !==
|
|
171
|
+
if (rank !== 9 - lines.length || file !== 9) return Result.err(new KifError(InvalidKif.Board));
|
|
164
172
|
return Result.ok(board);
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
export function parseKifHand(
|
|
168
|
-
const
|
|
169
|
-
const pieces =
|
|
175
|
+
export function parseKifHand(handPart: string): Result<Hand, KifError> {
|
|
176
|
+
const hand = Hand.empty();
|
|
177
|
+
const pieces = handPart.replace(/ /g, ' ').trim().split(' ');
|
|
170
178
|
|
|
171
|
-
if (
|
|
179
|
+
if (handPart.includes('なし')) return Result.ok(hand);
|
|
172
180
|
for (const piece of pieces) {
|
|
173
181
|
for (let i = 0; i < piece.length; i++) {
|
|
174
182
|
const role = kanjiToRole(piece[i++]);
|
|
175
|
-
if (!role) return Result.err(new KifError(InvalidKif.
|
|
183
|
+
if (!role || !handRoles('shogi').includes(role)) return Result.err(new KifError(InvalidKif.Hands));
|
|
176
184
|
let countStr = '';
|
|
177
185
|
while (i < piece.length && ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'].includes(piece[i]))
|
|
178
186
|
countStr += piece[i++];
|
|
179
187
|
const count = kanjiToNumber(countStr) || 1;
|
|
180
|
-
|
|
188
|
+
hand[role] += count;
|
|
181
189
|
}
|
|
182
190
|
}
|
|
183
|
-
return Result.ok(
|
|
191
|
+
return Result.ok(hand);
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
export function parseTags(kif: string): [string, string][] {
|
|
@@ -204,7 +212,7 @@ export function parseKifMove(kifMove: string, lastDest: Square | undefined = und
|
|
|
204
212
|
const match = kifMove.match(/((?:[123456789][一二三四五六七八九]|同\s?))(飛|角|金|銀|桂|香|歩)打/);
|
|
205
213
|
if (!match) return;
|
|
206
214
|
const move = {
|
|
207
|
-
role: kanjiToRole(match[2])
|
|
215
|
+
role: kanjiToRole(match[2])!,
|
|
208
216
|
to: parseKifSquare(match[1]) ?? lastDest!,
|
|
209
217
|
};
|
|
210
218
|
return move;
|
package/src/kifHandicaps.ts
CHANGED
|
@@ -23,6 +23,9 @@ export function sfenToHandicapName(sfen: string): string | undefined {
|
|
|
23
23
|
return '八枚落ち';
|
|
24
24
|
case '4k4/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w -':
|
|
25
25
|
return '十枚落ち';
|
|
26
|
+
// minishogi
|
|
27
|
+
case 'rbsgk/4p/5/P4/KGSBR b -':
|
|
28
|
+
return '五々将棋';
|
|
26
29
|
default:
|
|
27
30
|
return undefined;
|
|
28
31
|
}
|
|
@@ -79,6 +82,8 @@ export function handicapNameToSfen(name: string): string | undefined {
|
|
|
79
82
|
return '2sgkgs2/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w RB2N2L 1';
|
|
80
83
|
case '八枚得':
|
|
81
84
|
return '3gkg3/9/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL w RB2S2N2L 1';
|
|
85
|
+
case '五々将棋':
|
|
86
|
+
return 'rbsgk/4p/5/P4/KGSBR b - 1';
|
|
82
87
|
default:
|
|
83
88
|
return undefined;
|
|
84
89
|
}
|
package/src/kifUtil.ts
CHANGED
|
File without changes
|
package/src/setup.ts
CHANGED
|
@@ -1,97 +1,10 @@
|
|
|
1
|
-
import { Color
|
|
1
|
+
import { Color } from './types';
|
|
2
2
|
import { Board } from './board';
|
|
3
|
-
|
|
4
|
-
export class MaterialSide {
|
|
5
|
-
pawn: number;
|
|
6
|
-
lance: number;
|
|
7
|
-
knight: number;
|
|
8
|
-
silver: number;
|
|
9
|
-
gold: number;
|
|
10
|
-
bishop: number;
|
|
11
|
-
rook: number;
|
|
12
|
-
|
|
13
|
-
private constructor() {}
|
|
14
|
-
|
|
15
|
-
static empty(): MaterialSide {
|
|
16
|
-
const m = new MaterialSide();
|
|
17
|
-
for (const role of POCKET_ROLES) m[role] = 0;
|
|
18
|
-
return m;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
static fromBoard(board: Board, color: Color): MaterialSide {
|
|
22
|
-
const m = new MaterialSide();
|
|
23
|
-
for (const role of POCKET_ROLES) m[role] = board.pieces(color, role).size();
|
|
24
|
-
return m;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
clone(): MaterialSide {
|
|
28
|
-
const m = new MaterialSide();
|
|
29
|
-
for (const role of POCKET_ROLES) m[role] = this[role];
|
|
30
|
-
return m;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
equals(other: MaterialSide): boolean {
|
|
34
|
-
return POCKET_ROLES.every(role => this[role] === other[role]);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
add(other: MaterialSide): MaterialSide {
|
|
38
|
-
const m = new MaterialSide();
|
|
39
|
-
for (const role of POCKET_ROLES) m[role] = this[role] + other[role];
|
|
40
|
-
return m;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
nonEmpty(): boolean {
|
|
44
|
-
return POCKET_ROLES.some(role => this[role] > 0);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
isEmpty(): boolean {
|
|
48
|
-
return !this.nonEmpty();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
count(): number {
|
|
52
|
-
return this.pawn + this.lance + this.knight + this.silver + this.gold + this.bishop + this.rook;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export class Material {
|
|
57
|
-
constructor(public gote: MaterialSide, public sente: MaterialSide) {}
|
|
58
|
-
|
|
59
|
-
static empty(): Material {
|
|
60
|
-
return new Material(MaterialSide.empty(), MaterialSide.empty());
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
static fromBoard(board: Board): Material {
|
|
64
|
-
return new Material(MaterialSide.fromBoard(board, 'gote'), MaterialSide.fromBoard(board, 'sente'));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
clone(): Material {
|
|
68
|
-
return new Material(this.gote.clone(), this.sente.clone());
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
equals(other: Material): boolean {
|
|
72
|
-
return this.gote.equals(other.gote) && this.sente.equals(other.sente);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
add(other: Material): Material {
|
|
76
|
-
return new Material(this.gote.add(other.gote), this.sente.add(other.sente));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
count(): number {
|
|
80
|
-
return this.gote.count() + this.sente.count();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
isEmpty(): boolean {
|
|
84
|
-
return this.gote.isEmpty() && this.sente.isEmpty();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
nonEmpty(): boolean {
|
|
88
|
-
return !this.isEmpty();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
3
|
+
import { Hands } from './hand';
|
|
91
4
|
|
|
92
5
|
export interface Setup {
|
|
93
6
|
board: Board;
|
|
94
|
-
|
|
7
|
+
hands: Hands;
|
|
95
8
|
turn: Color;
|
|
96
9
|
fullmoves: number;
|
|
97
10
|
}
|
|
@@ -99,7 +12,7 @@ export interface Setup {
|
|
|
99
12
|
export function defaultSetup(): Setup {
|
|
100
13
|
return {
|
|
101
14
|
board: Board.default(),
|
|
102
|
-
|
|
15
|
+
hands: Hands.empty(),
|
|
103
16
|
turn: 'sente',
|
|
104
17
|
fullmoves: 1,
|
|
105
18
|
};
|