shogiops 0.2.9 → 0.6.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/README.md +6 -7
- 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 +17 -173
- package/compat.js.map +1 -1
- package/debug.js +30 -56
- package/debug.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 +13 -10
- package/index.js +14 -15
- package/index.js.map +1 -1
- package/{csa.d.ts → notation/csa/csa.d.ts} +9 -9
- package/{csa.js → notation/csa/csa.js} +52 -56
- package/notation/csa/csa.js.map +1 -0
- package/notation/japanese.d.ts +3 -0
- package/notation/japanese.js +65 -0
- package/notation/japanese.js.map +1 -0
- package/{kif.d.ts → notation/kif/kif.d.ts} +10 -9
- package/{kif.js → notation/kif/kif.js} +86 -84
- 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} +5 -0
- package/notation/kif/kifHandicaps.js.map +1 -0
- package/notation/kitaoKawasaki.d.ts +3 -0
- package/notation/kitaoKawasaki.js +30 -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} +43 -32
- package/notation/notationUtil.js.map +1 -0
- package/notation/western.d.ts +3 -0
- package/notation/western.js +27 -0
- package/notation/western.js.map +1 -0
- package/notation/westernEngine.d.ts +3 -0
- package/notation/westernEngine.js +27 -0
- package/notation/westernEngine.js.map +1 -0
- package/package.json +51 -36
- package/setup.d.ts +2 -32
- package/setup.js +3 -74
- package/setup.js.map +1 -1
- package/sfen.d.ts +33 -0
- package/sfen.js +201 -0
- package/sfen.js.map +1 -0
- 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/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} +50 -51
- 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 +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 +4 -7
- package/util.js +9 -60
- 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/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.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/shogi.ts
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
import { Result } from '@badrap/result';
|
|
2
|
-
import {
|
|
3
|
-
Rules,
|
|
4
|
-
Color,
|
|
5
|
-
COLORS,
|
|
6
|
-
Square,
|
|
7
|
-
Move,
|
|
8
|
-
isDrop,
|
|
9
|
-
Piece,
|
|
10
|
-
Outcome,
|
|
11
|
-
POCKET_ROLES,
|
|
12
|
-
PROMOTABLE_ROLES,
|
|
13
|
-
PocketRole,
|
|
14
|
-
} from './types';
|
|
2
|
+
import { Rules, Color, COLORS, Square, Move, isDrop, Piece, Outcome, Role } from './types';
|
|
15
3
|
import { SquareSet } from './squareSet';
|
|
16
4
|
import { Board } from './board';
|
|
17
|
-
import { Setup
|
|
5
|
+
import { Setup } from './setup';
|
|
18
6
|
import {
|
|
19
7
|
bishopAttacks,
|
|
20
8
|
rookAttacks,
|
|
@@ -29,12 +17,26 @@ import {
|
|
|
29
17
|
horseAttacks,
|
|
30
18
|
dragonAttacks,
|
|
31
19
|
} from './attacks';
|
|
32
|
-
import { opposite, defined
|
|
20
|
+
import { opposite, defined } from './util';
|
|
21
|
+
import { Hands } from './hand';
|
|
22
|
+
import {
|
|
23
|
+
allRoles,
|
|
24
|
+
backrank,
|
|
25
|
+
handRoles,
|
|
26
|
+
pieceCanPromote,
|
|
27
|
+
pieceInDeadZone,
|
|
28
|
+
promote,
|
|
29
|
+
promotionZone,
|
|
30
|
+
secondBackrank,
|
|
31
|
+
unpromote,
|
|
32
|
+
} from './variantUtil';
|
|
33
33
|
|
|
34
34
|
export enum IllegalSetup {
|
|
35
35
|
Empty = 'ERR_EMPTY',
|
|
36
36
|
OppositeCheck = 'ERR_OPPOSITE_CHECK',
|
|
37
37
|
ImpossibleCheck = 'ERR_IMPOSSIBLE_CHECK',
|
|
38
|
+
InvalidPieces = 'ERR_INVALID_PIECE',
|
|
39
|
+
InvalidPiecesHand = 'ERR_INVALID_PIECE_IN_HAND',
|
|
38
40
|
InvalidPiecesPromotionZone = 'ERR_PIECES_MUST_PROMOTE',
|
|
39
41
|
Kings = 'ERR_KINGS',
|
|
40
42
|
Variant = 'ERR_VARIANT',
|
|
@@ -72,7 +74,7 @@ export interface Context {
|
|
|
72
74
|
|
|
73
75
|
export abstract class Position {
|
|
74
76
|
board: Board;
|
|
75
|
-
|
|
77
|
+
hands: Hands;
|
|
76
78
|
turn: Color;
|
|
77
79
|
fullmoves: number;
|
|
78
80
|
lastMove: Move | undefined;
|
|
@@ -82,9 +84,11 @@ export abstract class Position {
|
|
|
82
84
|
// When subclassing:
|
|
83
85
|
// - static default()
|
|
84
86
|
// - static fromSetup()
|
|
87
|
+
// - static promotable
|
|
88
|
+
// - static handRoles
|
|
85
89
|
// - Proper signature for clone()
|
|
86
90
|
|
|
87
|
-
abstract dropDests(role:
|
|
91
|
+
abstract dropDests(role: Role, ctx?: Context): SquareSet;
|
|
88
92
|
abstract dests(square: Square, ctx?: Context): SquareSet;
|
|
89
93
|
abstract isVariantEnd(): boolean;
|
|
90
94
|
abstract variantOutcome(ctx?: Context): Outcome | undefined;
|
|
@@ -94,9 +98,12 @@ export abstract class Position {
|
|
|
94
98
|
return attacksTo(square, attacker, this.board, occupied);
|
|
95
99
|
}
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
abstract numberOfFiles: number;
|
|
102
|
+
abstract numberOfRanks: number;
|
|
103
|
+
|
|
104
|
+
protected playCaptureAt(captured: Piece): void {
|
|
105
|
+
const unpromotedRole = unpromote(this.rules)(captured.role);
|
|
106
|
+
this.hands[opposite(captured.color)][unpromotedRole]++;
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
ctx(): Context {
|
|
@@ -137,7 +144,7 @@ export abstract class Position {
|
|
|
137
144
|
clone(): Position {
|
|
138
145
|
const pos = new (this as any).constructor();
|
|
139
146
|
pos.board = this.board.clone();
|
|
140
|
-
pos.
|
|
147
|
+
pos.hands = this.hands.clone();
|
|
141
148
|
pos.turn = this.turn;
|
|
142
149
|
pos.fullmoves = this.fullmoves;
|
|
143
150
|
pos.lastMove = this.lastMove;
|
|
@@ -146,15 +153,17 @@ export abstract class Position {
|
|
|
146
153
|
|
|
147
154
|
equalsIgnoreMoves(other: Position): boolean {
|
|
148
155
|
return (
|
|
149
|
-
|
|
150
|
-
this.board.equals(other.board) &&
|
|
156
|
+
this.rules === other.rules &&
|
|
157
|
+
this.board.equals(other.board) &&
|
|
158
|
+
this.hands.equals(other.hands) &&
|
|
159
|
+
this.turn === other.turn
|
|
151
160
|
);
|
|
152
161
|
}
|
|
153
162
|
|
|
154
163
|
toSetup(): Setup {
|
|
155
164
|
return {
|
|
156
165
|
board: this.board.clone(),
|
|
157
|
-
|
|
166
|
+
hands: this.hands.clone(),
|
|
158
167
|
turn: this.turn,
|
|
159
168
|
fullmoves: Math.min(Math.max(this.fullmoves, 1), 9999),
|
|
160
169
|
};
|
|
@@ -169,37 +178,24 @@ export abstract class Position {
|
|
|
169
178
|
for (const square of this.board[this.turn]) {
|
|
170
179
|
if (this.dests(square, ctx).nonEmpty()) return true;
|
|
171
180
|
}
|
|
172
|
-
for (const prole of
|
|
173
|
-
if (this.
|
|
181
|
+
for (const prole of handRoles(this.rules)) {
|
|
182
|
+
if (this.hands[this.turn][prole] > 0 && this.dropDests(prole, ctx).nonEmpty()) return true;
|
|
174
183
|
}
|
|
175
184
|
return false;
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
isLegal(move: Move, ctx?: Context): boolean {
|
|
179
188
|
if (isDrop(move)) {
|
|
180
|
-
const role = move.role
|
|
181
|
-
if (!defined(role) || this.
|
|
189
|
+
const role = move.role;
|
|
190
|
+
if (!defined(role) || !handRoles(this.rules).includes(role) || this.hands[this.turn][role] <= 0) return false;
|
|
182
191
|
return this.dropDests(role, ctx).has(move.to);
|
|
183
192
|
} else {
|
|
184
|
-
const
|
|
185
|
-
if (!role) return false;
|
|
193
|
+
const piece = this.board.get(move.from);
|
|
194
|
+
if (!piece || !allRoles(this.rules).includes(piece.role)) return false;
|
|
186
195
|
|
|
187
|
-
// Checking whether
|
|
188
|
-
if (move.promotion && !(
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
move.promotion &&
|
|
192
|
-
!(SquareSet.promotionZone(this.turn).has(move.to) || SquareSet.promotionZone(this.turn).has(move.from))
|
|
193
|
-
)
|
|
194
|
-
return false;
|
|
195
|
-
// Checking whether the promotion must be forced
|
|
196
|
-
if (
|
|
197
|
-
!move.promotion &&
|
|
198
|
-
defined(role) &&
|
|
199
|
-
(((role === 'pawn' || role === 'lance') && SquareSet.backrank(this.turn).has(move.to)) ||
|
|
200
|
-
(role === 'knight' && SquareSet.backrank2(this.turn).has(move.to)))
|
|
201
|
-
)
|
|
202
|
-
return false;
|
|
196
|
+
// Checking whether we can promote
|
|
197
|
+
if (move.promotion && !pieceCanPromote(this.rules)(piece, move.from, move.to)) return false;
|
|
198
|
+
if (!move.promotion && pieceInDeadZone(this.rules)(piece, move.to)) return false;
|
|
203
199
|
|
|
204
200
|
const dests = this.dests(move.from, ctx);
|
|
205
201
|
return dests.has(move.to);
|
|
@@ -227,7 +223,7 @@ export abstract class Position {
|
|
|
227
223
|
}
|
|
228
224
|
|
|
229
225
|
isImpasse(): boolean {
|
|
230
|
-
const allPiecesInPromotionZone =
|
|
226
|
+
const allPiecesInPromotionZone = promotionZone(this.rules)(this.turn).intersect(this.board[this.turn]);
|
|
231
227
|
const majorPiecesInPromotionZone = allPiecesInPromotionZone.intersect(
|
|
232
228
|
this.board.bishop.union(this.board.rook).union(this.board.horse).union(this.board.dragon)
|
|
233
229
|
);
|
|
@@ -237,8 +233,8 @@ export abstract class Position {
|
|
|
237
233
|
allPiecesInPromotionZone.size() -
|
|
238
234
|
1 +
|
|
239
235
|
majorPiecesInPromotionZone.size() * 4 +
|
|
240
|
-
this.
|
|
241
|
-
(this.
|
|
236
|
+
this.hands[this.turn].count() +
|
|
237
|
+
(this.hands[this.turn].bishop + this.hands[this.turn].rook) * 4;
|
|
242
238
|
|
|
243
239
|
return (
|
|
244
240
|
defined(king) &&
|
|
@@ -269,12 +265,12 @@ export abstract class Position {
|
|
|
269
265
|
return d;
|
|
270
266
|
}
|
|
271
267
|
|
|
272
|
-
allDropDests(ctx?: Context): Map<
|
|
268
|
+
allDropDests(ctx?: Context): Map<Role, SquareSet> {
|
|
273
269
|
ctx = ctx || this.ctx();
|
|
274
270
|
const d = new Map();
|
|
275
271
|
if (ctx.variantEnd) return d;
|
|
276
|
-
for (const prole of
|
|
277
|
-
if (this.
|
|
272
|
+
for (const prole of handRoles(this.rules)) {
|
|
273
|
+
if (this.hands[this.turn][prole] > 0) d.set(prole, this.dropDests(prole, ctx));
|
|
278
274
|
else d.set(prole, SquareSet.empty());
|
|
279
275
|
}
|
|
280
276
|
return d;
|
|
@@ -289,21 +285,19 @@ export abstract class Position {
|
|
|
289
285
|
|
|
290
286
|
if (isDrop(move)) {
|
|
291
287
|
this.board.set(move.to, { role: move.role, color: turn });
|
|
292
|
-
this.
|
|
288
|
+
this.hands[turn][move.role]--;
|
|
293
289
|
} else {
|
|
294
290
|
const piece = this.board.take(move.from);
|
|
295
291
|
if (!piece) return;
|
|
296
292
|
const role = piece.role;
|
|
297
293
|
if (
|
|
298
|
-
(move.promotion && (
|
|
299
|
-
(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
piece.role = promote(role);
|
|
303
|
-
}
|
|
294
|
+
(move.promotion && pieceCanPromote(this.rules)(piece, move.from, move.to)) ||
|
|
295
|
+
pieceInDeadZone(this.rules)(piece, move.to)
|
|
296
|
+
)
|
|
297
|
+
piece.role = promote(this.rules)(role);
|
|
304
298
|
|
|
305
299
|
const capture = this.board.set(move.to, piece);
|
|
306
|
-
if (capture) this.playCaptureAt(
|
|
300
|
+
if (capture) this.playCaptureAt(capture);
|
|
307
301
|
}
|
|
308
302
|
}
|
|
309
303
|
}
|
|
@@ -313,10 +307,13 @@ export class Shogi extends Position {
|
|
|
313
307
|
super(rules || 'shogi');
|
|
314
308
|
}
|
|
315
309
|
|
|
310
|
+
numberOfRanks = 9;
|
|
311
|
+
numberOfFiles = 9;
|
|
312
|
+
|
|
316
313
|
static default(): Shogi {
|
|
317
314
|
const pos = new this();
|
|
318
315
|
pos.board = Board.default();
|
|
319
|
-
pos.
|
|
316
|
+
pos.hands = Hands.empty();
|
|
320
317
|
pos.turn = 'sente';
|
|
321
318
|
pos.fullmoves = 1;
|
|
322
319
|
return pos;
|
|
@@ -325,7 +322,7 @@ export class Shogi extends Position {
|
|
|
325
322
|
static fromSetup(setup: Setup, strict = true): Result<Shogi, PositionError> {
|
|
326
323
|
const pos = new this();
|
|
327
324
|
pos.board = setup.board.clone();
|
|
328
|
-
pos.
|
|
325
|
+
pos.hands = setup.hands;
|
|
329
326
|
pos.turn = setup.turn;
|
|
330
327
|
pos.fullmoves = setup.fullmoves;
|
|
331
328
|
return pos.validate(strict).map(_ => pos);
|
|
@@ -336,6 +333,8 @@ export class Shogi extends Position {
|
|
|
336
333
|
}
|
|
337
334
|
|
|
338
335
|
protected validate(strict: boolean): Result<undefined, PositionError> {
|
|
336
|
+
if (this.board.numberOfRanks !== this.numberOfRanks || this.board.numberOfFiles !== this.numberOfFiles)
|
|
337
|
+
return Result.err(new PositionError(IllegalSetup.Empty));
|
|
339
338
|
if (!strict) return Result.ok(undefined);
|
|
340
339
|
if (this.board.occupied.isEmpty()) return Result.err(new PositionError(IllegalSetup.Empty));
|
|
341
340
|
if (this.board.king.size() < 1) return Result.err(new PositionError(IllegalSetup.Kings));
|
|
@@ -344,15 +343,16 @@ export class Shogi extends Position {
|
|
|
344
343
|
if (defined(otherKing) && this.kingAttackers(otherKing, this.turn, this.board.occupied).nonEmpty())
|
|
345
344
|
return Result.err(new PositionError(IllegalSetup.OppositeCheck));
|
|
346
345
|
|
|
347
|
-
const
|
|
346
|
+
for (const rn of this.hands.sente)
|
|
347
|
+
if (!handRoles(this.rules).includes(rn[0])) return Result.err(new PositionError(IllegalSetup.InvalidPiecesHand));
|
|
348
|
+
for (const rn of this.hands.gote)
|
|
349
|
+
if (!handRoles(this.rules).includes(rn[0])) return Result.err(new PositionError(IllegalSetup.InvalidPiecesHand));
|
|
348
350
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
)
|
|
355
|
-
return Result.err(new PositionError(IllegalSetup.InvalidPiecesPromotionZone));
|
|
351
|
+
for (const sp of this.board) {
|
|
352
|
+
if (!allRoles(this.rules).includes(sp[1].role)) return Result.err(new PositionError(IllegalSetup.InvalidPieces));
|
|
353
|
+
if (pieceInDeadZone(this.rules)(sp[1], sp[0]))
|
|
354
|
+
return Result.err(new PositionError(IllegalSetup.InvalidPiecesPromotionZone));
|
|
355
|
+
}
|
|
356
356
|
|
|
357
357
|
return this.validateCheckers();
|
|
358
358
|
}
|
|
@@ -368,12 +368,12 @@ export class Shogi extends Position {
|
|
|
368
368
|
return Result.ok(undefined);
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
-
dropDests(role:
|
|
371
|
+
dropDests(role: Role, ctx?: Context): SquareSet {
|
|
372
372
|
let mask = this.board.occupied.complement();
|
|
373
373
|
ctx = ctx || this.ctx();
|
|
374
|
-
// Removing backranks, where no legal
|
|
375
|
-
if (role === 'pawn' || role === 'lance') mask = mask.diff(
|
|
376
|
-
if (role === 'knight') mask = mask.diff(
|
|
374
|
+
// Removing backranks, where no legal drop would be possible
|
|
375
|
+
if (role === 'pawn' || role === 'lance' || role === 'knight') mask = mask.diff(backrank(this.rules)(this.turn));
|
|
376
|
+
if (role === 'knight') mask = mask.diff(secondBackrank(this.rules)(this.turn));
|
|
377
377
|
|
|
378
378
|
// Checking for double pawns
|
|
379
379
|
if (role === 'pawn') {
|
|
@@ -458,6 +458,6 @@ export class Shogi extends Position {
|
|
|
458
458
|
}
|
|
459
459
|
|
|
460
460
|
hasInsufficientMaterial(color: Color): boolean {
|
|
461
|
-
return this.board.occupied.intersect(this.board[color]).size() + this.
|
|
461
|
+
return this.board.occupied.intersect(this.board[color]).size() + this.hands[color].count() < 2; // sente king, gote king and one other piece
|
|
462
462
|
}
|
|
463
463
|
}
|
package/src/squareSet.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Square
|
|
1
|
+
import { Square } from './types';
|
|
2
2
|
|
|
3
3
|
function popcnt27(n: number): number {
|
|
4
4
|
n = trimTo27(n);
|
|
@@ -68,23 +68,6 @@ export class SquareSet implements Iterable<Square> {
|
|
|
68
68
|
return new SquareSet(0x00000101, 0, 0x04040000);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
static promotionZones(): SquareSet {
|
|
72
|
-
// backranks
|
|
73
|
-
return new SquareSet(0x07ffffff, 0, 0x07ffffff);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
static promotionZone(color: Color): SquareSet {
|
|
77
|
-
return color === 'sente' ? new SquareSet(0, 0, 0x07ffffff) : new SquareSet(0x07ffffff, 0, 0);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
static backrank(color: Color): SquareSet {
|
|
81
|
-
return color === 'sente' ? SquareSet.fromRank(8) : SquareSet.fromRank(0);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
static backrank2(color: Color): SquareSet {
|
|
85
|
-
return (color === 'sente' ? SquareSet.fromRank(7) : SquareSet.fromRank(1)).union(this.backrank(color));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
71
|
complement(): SquareSet {
|
|
89
72
|
return new SquareSet(~this.lo, ~this.mid, ~this.hi);
|
|
90
73
|
}
|
package/src/transform.ts
CHANGED
|
@@ -24,6 +24,8 @@ export function rotate180(s: SquareSet): SquareSet {
|
|
|
24
24
|
|
|
25
25
|
export function transformBoard(board: Board, f: (s: SquareSet) => SquareSet): Board {
|
|
26
26
|
const b = Board.empty();
|
|
27
|
+
b.numberOfFiles = board.numberOfFiles;
|
|
28
|
+
b.numberOfRanks = board.numberOfRanks;
|
|
27
29
|
b.occupied = f(board.occupied);
|
|
28
30
|
for (const color of COLORS) b[color] = f(board[color]);
|
|
29
31
|
for (const role of ROLES) b[role] = f(board[role]);
|
|
@@ -33,7 +35,7 @@ export function transformBoard(board: Board, f: (s: SquareSet) => SquareSet): Bo
|
|
|
33
35
|
export function transformSetup(setup: Setup, f: (s: SquareSet) => SquareSet): Setup {
|
|
34
36
|
return {
|
|
35
37
|
board: transformBoard(setup.board, f),
|
|
36
|
-
|
|
38
|
+
hands: setup.hands.clone(),
|
|
37
39
|
turn: setup.turn,
|
|
38
40
|
fullmoves: setup.fullmoves,
|
|
39
41
|
};
|
package/src/types.ts
CHANGED
|
@@ -18,27 +18,24 @@ export type ByColor<T> = {
|
|
|
18
18
|
[color in Color]: T;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
// correct order for sfen hand exporting
|
|
21
22
|
export const ROLES = [
|
|
22
|
-
'pawn',
|
|
23
|
-
'lance',
|
|
24
|
-
'knight',
|
|
25
|
-
'silver',
|
|
26
|
-
'gold',
|
|
27
|
-
'bishop',
|
|
28
23
|
'rook',
|
|
24
|
+
'bishop',
|
|
25
|
+
'gold',
|
|
26
|
+
'silver',
|
|
27
|
+
'knight',
|
|
28
|
+
'lance',
|
|
29
|
+
'pawn',
|
|
30
|
+
'dragon',
|
|
31
|
+
'horse',
|
|
29
32
|
'tokin',
|
|
30
|
-
'promotedlance',
|
|
31
|
-
'promotedknight',
|
|
32
33
|
'promotedsilver',
|
|
33
|
-
'
|
|
34
|
-
'
|
|
34
|
+
'promotedknight',
|
|
35
|
+
'promotedlance',
|
|
35
36
|
'king',
|
|
36
37
|
] as const;
|
|
37
|
-
export const POCKET_ROLES = ['rook', 'bishop', 'gold', 'silver', 'knight', 'lance', 'pawn'] as const;
|
|
38
|
-
export const PROMOTABLE_ROLES = ['pawn', 'lance', 'knight', 'silver', 'bishop', 'rook'] as const;
|
|
39
38
|
export type Role = typeof ROLES[number];
|
|
40
|
-
export type PocketRole = typeof POCKET_ROLES[number];
|
|
41
|
-
export type PromotableRole = typeof PROMOTABLE_ROLES[number];
|
|
42
39
|
|
|
43
40
|
export type ByRole<T> = {
|
|
44
41
|
[role in Role]: T;
|
|
@@ -56,7 +53,7 @@ export interface NormalMove {
|
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
export interface DropMove {
|
|
59
|
-
role:
|
|
56
|
+
role: Role;
|
|
60
57
|
to: Square;
|
|
61
58
|
}
|
|
62
59
|
|
|
@@ -71,7 +68,7 @@ export function isNormal(v: Move): v is NormalMove {
|
|
|
71
68
|
}
|
|
72
69
|
|
|
73
70
|
// variant will be added later, once lishogi supports them
|
|
74
|
-
export const RULES = ['shogi'] as const;
|
|
71
|
+
export const RULES = ['shogi', 'minishogi'] as const;
|
|
75
72
|
export type Rules = typeof RULES[number];
|
|
76
73
|
|
|
77
74
|
export interface Outcome {
|
package/src/util.ts
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
FILE_NAMES,
|
|
4
|
-
RANK_NAMES,
|
|
5
|
-
Color,
|
|
6
|
-
Square,
|
|
7
|
-
Role,
|
|
8
|
-
PocketRole,
|
|
9
|
-
Move,
|
|
10
|
-
isDrop,
|
|
11
|
-
SquareName,
|
|
12
|
-
Piece,
|
|
13
|
-
PROMOTABLE_ROLES,
|
|
14
|
-
} from './types';
|
|
1
|
+
import { FILE_NAMES, RANK_NAMES, Color, Square, Role, Move, isDrop, SquareName } from './types';
|
|
15
2
|
|
|
16
3
|
export function defined<A>(v: A | undefined): v is A {
|
|
17
4
|
return v !== undefined;
|
|
@@ -21,6 +8,7 @@ export function opposite(color: Color): Color {
|
|
|
21
8
|
return color === 'gote' ? 'sente' : 'gote';
|
|
22
9
|
}
|
|
23
10
|
|
|
11
|
+
// coordinate system starts at bottom left
|
|
24
12
|
export function squareRank(square: Square): number {
|
|
25
13
|
return Math.floor(square / 9);
|
|
26
14
|
}
|
|
@@ -29,53 +17,7 @@ export function squareFile(square: Square): number {
|
|
|
29
17
|
return square % 9;
|
|
30
18
|
}
|
|
31
19
|
|
|
32
|
-
export function
|
|
33
|
-
switch (role) {
|
|
34
|
-
case 'pawn':
|
|
35
|
-
case 'tokin':
|
|
36
|
-
return 'pawn';
|
|
37
|
-
case 'lance':
|
|
38
|
-
case 'promotedlance':
|
|
39
|
-
return 'lance';
|
|
40
|
-
case 'knight':
|
|
41
|
-
case 'promotedknight':
|
|
42
|
-
return 'knight';
|
|
43
|
-
case 'silver':
|
|
44
|
-
case 'promotedsilver':
|
|
45
|
-
return 'silver';
|
|
46
|
-
case 'gold':
|
|
47
|
-
return 'gold';
|
|
48
|
-
case 'bishop':
|
|
49
|
-
case 'horse':
|
|
50
|
-
return 'bishop';
|
|
51
|
-
case 'rook':
|
|
52
|
-
case 'dragon':
|
|
53
|
-
return 'rook';
|
|
54
|
-
default:
|
|
55
|
-
return role;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function promote(role: Role): Role {
|
|
60
|
-
switch (role) {
|
|
61
|
-
case 'pawn':
|
|
62
|
-
return 'tokin';
|
|
63
|
-
case 'lance':
|
|
64
|
-
return 'promotedlance';
|
|
65
|
-
case 'knight':
|
|
66
|
-
return 'promotedknight';
|
|
67
|
-
case 'silver':
|
|
68
|
-
return 'promotedsilver';
|
|
69
|
-
case 'bishop':
|
|
70
|
-
return 'horse';
|
|
71
|
-
case 'rook':
|
|
72
|
-
return 'dragon';
|
|
73
|
-
default:
|
|
74
|
-
return role;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function roleToChar(role: Role): string {
|
|
20
|
+
export function roleToString(role: Role): string {
|
|
79
21
|
switch (role) {
|
|
80
22
|
case 'pawn':
|
|
81
23
|
return 'p';
|
|
@@ -261,7 +203,7 @@ export function kanjiToRole(str: string): Role | undefined {
|
|
|
261
203
|
}
|
|
262
204
|
}
|
|
263
205
|
|
|
264
|
-
export function
|
|
206
|
+
export function stringToRole(
|
|
265
207
|
ch:
|
|
266
208
|
| 'p'
|
|
267
209
|
| 'l'
|
|
@@ -290,8 +232,8 @@ export function charToRole(
|
|
|
290
232
|
| '+B'
|
|
291
233
|
| '+R'
|
|
292
234
|
): Role;
|
|
293
|
-
export function
|
|
294
|
-
export function
|
|
235
|
+
export function stringToRole(ch: string): Role | undefined;
|
|
236
|
+
export function stringToRole(ch: string): Role | undefined {
|
|
295
237
|
switch (ch) {
|
|
296
238
|
case 'P':
|
|
297
239
|
case 'p':
|
|
@@ -356,7 +298,7 @@ export function makeSquare(square: Square): SquareName {
|
|
|
356
298
|
|
|
357
299
|
export function parseUsi(str: string): Move | undefined {
|
|
358
300
|
if (str[1] === '*' && str.length === 4) {
|
|
359
|
-
const role =
|
|
301
|
+
const role = stringToRole(str[0]);
|
|
360
302
|
const to = parseSquare(str.slice(2));
|
|
361
303
|
if (defined(role) && defined(to)) return { role, to };
|
|
362
304
|
} else if (str.length === 4 || str.length === 5) {
|
|
@@ -369,17 +311,10 @@ export function parseUsi(str: string): Move | undefined {
|
|
|
369
311
|
}
|
|
370
312
|
|
|
371
313
|
export function makeUsi(move: Move): string {
|
|
372
|
-
if (isDrop(move)) return `${
|
|
314
|
+
if (isDrop(move)) return `${roleToString(move.role).toUpperCase()}*${makeSquare(move.to)}`;
|
|
373
315
|
return makeSquare(move.from) + makeSquare(move.to) + (move.promotion ? '+' : '');
|
|
374
316
|
}
|
|
375
317
|
|
|
376
|
-
export function canPiecePromote(piece: Piece, from: Square, to: Square): boolean {
|
|
377
|
-
return (
|
|
378
|
-
(PROMOTABLE_ROLES as ReadonlyArray<string>).includes(piece.role) &&
|
|
379
|
-
(SquareSet.promotionZone(piece.color).has(from) || SquareSet.promotionZone(piece.color).has(to))
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
318
|
export function toBW(color: string): 'b' | 'w' {
|
|
384
319
|
// white, w, gote, g
|
|
385
320
|
if (color[0] === 'w' || color[0] === 'g') return 'w';
|
package/src/variant.ts
CHANGED
|
@@ -1,20 +1,61 @@
|
|
|
1
1
|
import { Result } from '@badrap/result';
|
|
2
|
-
import { Rules } from './types';
|
|
2
|
+
import { Role, Rules, Square } from './types';
|
|
3
3
|
import { Setup } from './setup';
|
|
4
4
|
import { PositionError, Position, IllegalSetup, Context, Shogi } from './shogi';
|
|
5
|
+
import { SquareSet } from './squareSet';
|
|
6
|
+
import { Board } from './board';
|
|
7
|
+
import { Hands } from './hand';
|
|
5
8
|
|
|
6
9
|
export { Position, PositionError, IllegalSetup, Context, Shogi };
|
|
7
10
|
|
|
8
11
|
export function defaultPosition(rules: Rules): Position {
|
|
9
12
|
switch (rules) {
|
|
10
|
-
case '
|
|
13
|
+
case 'minishogi':
|
|
14
|
+
return Minishogi.default();
|
|
15
|
+
default:
|
|
11
16
|
return Shogi.default();
|
|
12
17
|
}
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
export function setupPosition(rules: Rules, setup: Setup, strict = true): Result<Position, PositionError> {
|
|
16
21
|
switch (rules) {
|
|
17
|
-
case '
|
|
22
|
+
case 'minishogi':
|
|
23
|
+
return Minishogi.fromSetup(setup, strict);
|
|
24
|
+
default:
|
|
18
25
|
return Shogi.fromSetup(setup, strict);
|
|
19
26
|
}
|
|
20
27
|
}
|
|
28
|
+
|
|
29
|
+
export class Minishogi extends Shogi {
|
|
30
|
+
protected constructor() {
|
|
31
|
+
super('minishogi');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
numberOfRanks = 5;
|
|
35
|
+
numberOfFiles = 5;
|
|
36
|
+
|
|
37
|
+
static default(): Minishogi {
|
|
38
|
+
const pos = new this();
|
|
39
|
+
pos.board = Board.minishogi();
|
|
40
|
+
pos.hands = Hands.empty();
|
|
41
|
+
pos.turn = 'sente';
|
|
42
|
+
pos.fullmoves = 1;
|
|
43
|
+
return pos;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static fromSetup(setup: Setup, strict: boolean): Result<Minishogi, PositionError> {
|
|
47
|
+
return super.fromSetup(setup, strict) as Result<Minishogi, PositionError>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
clone(): Minishogi {
|
|
51
|
+
return super.clone() as Minishogi;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
dests(square: Square, ctx?: Context): SquareSet {
|
|
55
|
+
return super.dests(square, ctx).intersect(new SquareSet(0x0, 0x7c3e000, 0x7c3e1f0));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
dropDests(role: Role, ctx?: Context): SquareSet {
|
|
59
|
+
return super.dropDests(role, ctx).intersect(new SquareSet(0x0, 0x7c3e000, 0x7c3e1f0));
|
|
60
|
+
}
|
|
61
|
+
}
|