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.
Files changed (114) hide show
  1. package/README.md +6 -7
  2. package/attacks.js +7 -7
  3. package/attacks.js.map +1 -1
  4. package/board.d.ts +3 -0
  5. package/board.js +31 -0
  6. package/board.js.map +1 -1
  7. package/compat.d.ts +6 -24
  8. package/compat.js +17 -173
  9. package/compat.js.map +1 -1
  10. package/debug.js +30 -56
  11. package/debug.js.map +1 -1
  12. package/hand.d.ts +38 -0
  13. package/hand.js +74 -0
  14. package/hand.js.map +1 -0
  15. package/hash.d.ts +4 -3
  16. package/hash.js +9 -9
  17. package/hash.js.map +1 -1
  18. package/index.d.ts +13 -10
  19. package/index.js +14 -15
  20. package/index.js.map +1 -1
  21. package/{csa.d.ts → notation/csa/csa.d.ts} +9 -9
  22. package/{csa.js → notation/csa/csa.js} +52 -56
  23. package/notation/csa/csa.js.map +1 -0
  24. package/notation/japanese.d.ts +3 -0
  25. package/notation/japanese.js +65 -0
  26. package/notation/japanese.js.map +1 -0
  27. package/{kif.d.ts → notation/kif/kif.d.ts} +10 -9
  28. package/{kif.js → notation/kif/kif.js} +86 -84
  29. package/notation/kif/kif.js.map +1 -0
  30. package/{kifHandicaps.d.ts → notation/kif/kifHandicaps.d.ts} +0 -0
  31. package/{kifHandicaps.js → notation/kif/kifHandicaps.js} +5 -0
  32. package/notation/kif/kifHandicaps.js.map +1 -0
  33. package/notation/kitaoKawasaki.d.ts +3 -0
  34. package/notation/kitaoKawasaki.js +30 -0
  35. package/notation/kitaoKawasaki.js.map +1 -0
  36. package/notation/kitaokawasaki.d.ts +0 -0
  37. package/notation/kitaokawasaki.js +2 -0
  38. package/notation/kitaokawasaki.js.map +1 -0
  39. package/notation/notationUtil.d.ts +12 -0
  40. package/{kifUtil.js → notation/notationUtil.js} +43 -32
  41. package/notation/notationUtil.js.map +1 -0
  42. package/notation/western.d.ts +3 -0
  43. package/notation/western.js +27 -0
  44. package/notation/western.js.map +1 -0
  45. package/notation/westernEngine.d.ts +3 -0
  46. package/notation/westernEngine.js +27 -0
  47. package/notation/westernEngine.js.map +1 -0
  48. package/package.json +51 -36
  49. package/setup.d.ts +2 -32
  50. package/setup.js +3 -74
  51. package/setup.js.map +1 -1
  52. package/sfen.d.ts +33 -0
  53. package/sfen.js +201 -0
  54. package/sfen.js.map +1 -0
  55. package/shogi.d.ts +14 -7
  56. package/shogi.js +109 -106
  57. package/shogi.js.map +1 -1
  58. package/squareSet.d.ts +1 -5
  59. package/squareSet.js +0 -13
  60. package/squareSet.js.map +1 -1
  61. package/src/board.ts +34 -0
  62. package/src/compat.ts +18 -197
  63. package/src/debug.ts +26 -56
  64. package/src/hand.ts +94 -0
  65. package/src/hash.ts +8 -7
  66. package/src/index.ts +16 -12
  67. package/src/{csa.ts → notation/csa/csa.ts} +50 -51
  68. package/src/notation/japanese.ts +74 -0
  69. package/src/{kif.ts → notation/kif/kif.ts} +77 -78
  70. package/src/{kifHandicaps.ts → notation/kif/kifHandicaps.ts} +5 -0
  71. package/src/notation/kitaoKawasaki.ts +24 -0
  72. package/src/{kifUtil.ts → notation/notationUtil.ts} +37 -28
  73. package/src/notation/western.ts +21 -0
  74. package/src/notation/westernEngine.ts +21 -0
  75. package/src/setup.ts +4 -91
  76. package/src/sfen.ts +190 -0
  77. package/src/shogi.ts +75 -75
  78. package/src/squareSet.ts +1 -18
  79. package/src/transform.ts +3 -1
  80. package/src/types.ts +13 -16
  81. package/src/util.ts +8 -73
  82. package/src/variant.ts +44 -3
  83. package/src/variantUtil.ts +160 -0
  84. package/transform.js +3 -1
  85. package/transform.js.map +1 -1
  86. package/types.d.ts +3 -7
  87. package/types.js +13 -14
  88. package/types.js.map +1 -1
  89. package/util.d.ts +4 -7
  90. package/util.js +9 -60
  91. package/util.js.map +1 -1
  92. package/variant.d.ts +12 -1
  93. package/variant.js +38 -3
  94. package/variant.js.map +1 -1
  95. package/variantUtil.d.ts +13 -0
  96. package/variantUtil.js +160 -0
  97. package/variantUtil.js.map +1 -0
  98. package/csa.js.map +0 -1
  99. package/csaUtil.d.ts +0 -3
  100. package/csaUtil.js +0 -21
  101. package/csaUtil.js.map +0 -1
  102. package/fen.d.ts +0 -32
  103. package/fen.js +0 -197
  104. package/fen.js.map +0 -1
  105. package/kif.js.map +0 -1
  106. package/kifHandicaps.js.map +0 -1
  107. package/kifUtil.d.ts +0 -9
  108. package/kifUtil.js.map +0 -1
  109. package/san.d.ts +0 -6
  110. package/san.js +0 -135
  111. package/san.js.map +0 -1
  112. package/src/csaUtil.ts +0 -15
  113. package/src/fen.ts +0 -183
  114. 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, Material } from './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, unpromote, promote } from './util';
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
- pockets: Material;
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: PocketRole, ctx?: Context): SquareSet;
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
- protected playCaptureAt(square: Square, captured: Piece): void {
98
- const unpromotedRole = unpromote(captured.role);
99
- if (unpromotedRole !== 'king') this.pockets[opposite(captured.color)][unpromotedRole]++;
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.pockets = this.pockets.clone();
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
- // this.rules === other.rules // variants
150
- this.board.equals(other.board) && this.pockets.equals(other.pockets) && this.turn === other.turn
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
- pockets: this.pockets.clone(),
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 POCKET_ROLES) {
173
- if (this.pockets[this.turn][prole] > 0 && this.dropDests(prole, ctx).nonEmpty()) return true;
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 as PocketRole;
181
- if (!defined(role) || this.pockets[this.turn][role] <= 0) return false;
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 role = this.board.getRole(move.from);
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 this role can be promoted
188
- if (move.promotion && !(PROMOTABLE_ROLES as ReadonlyArray<string>).includes(role)) return false;
189
- // Checking whether piece is entering/leaving the promotion zone
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 = SquareSet.promotionZone(this.turn).intersect(this.board[this.turn]);
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.pockets[this.turn].count() +
241
- (this.pockets[this.turn].bishop + this.pockets[this.turn].rook) * 4;
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<PocketRole, SquareSet> {
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 POCKET_ROLES) {
277
- if (this.pockets[this.turn][prole] > 0) d.set(prole, this.dropDests(prole, ctx));
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.pockets[turn][move.role]--;
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 && (PROMOTABLE_ROLES as ReadonlyArray<string>).includes(role)) ||
299
- (role === 'knight' && SquareSet.backrank2(turn).has(move.to)) ||
300
- ((role === 'pawn' || role === 'lance') && SquareSet.backrank(turn).has(move.to))
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(move.to, capture);
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.pockets = Material.empty();
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.pockets = setup.pockets;
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 b1 = this.board.pawn.union(this.board.lance);
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
- if (
350
- SquareSet.backrank('sente').intersect(b1.intersect(this.board['sente'])).nonEmpty() ||
351
- SquareSet.backrank('gote').intersect(b1.intersect(this.board['gote'])).nonEmpty() ||
352
- SquareSet.backrank2('sente').intersect(this.board.knight.intersect(this.board['sente'])).nonEmpty() ||
353
- SquareSet.backrank2('gote').intersect(this.board.knight.intersect(this.board['gote'])).nonEmpty()
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: PocketRole, ctx?: Context): SquareSet {
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 moves would be possible
375
- if (role === 'pawn' || role === 'lance') mask = mask.diff(SquareSet.backrank(this.turn));
376
- if (role === 'knight') mask = mask.diff(SquareSet.backrank2(this.turn));
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.pockets[color].count() < 2; // sente king, gote king and one other piece
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, Color } from './types';
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
- pockets: setup.pockets.clone(),
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
- 'horse',
34
- 'dragon',
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: PocketRole;
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 { SquareSet } from './squareSet';
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 unpromote(role: Role): PocketRole | 'king' {
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 charToRole(
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 charToRole(ch: string): Role | undefined;
294
- export function charToRole(ch: string): Role | undefined {
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 = charToRole(str[0]) as PocketRole;
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 `${roleToChar(move.role).toUpperCase()}*${makeSquare(move.to)}`;
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 'shogi':
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 'shogi':
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
+ }