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
@@ -1,10 +1,14 @@
1
1
  import { Result } from '@badrap/result';
2
- import { Board } from './board';
3
- import { Material, MaterialSide, Setup } from './setup';
4
- import { Position } from './shogi';
5
- import { Color, isDrop, Move, PocketRole, POCKET_ROLES } from './types';
6
- import { csaToRole, defined, promote, roleToCsa } from './util';
7
- import { makeCsaSquare, parseCsaSquare } from './csaUtil';
2
+ import { Board } from '../../board';
3
+ import { Setup } from '../../setup';
4
+ import { Position } from '../../shogi';
5
+ import { Color, isDrop, Move } from '../../types';
6
+ import { csaToRole, defined, roleToCsa } from '../../util';
7
+ import { Hand, Hands } from '../../hand';
8
+ import { allRoles, handRoles, promote } from '../../variantUtil';
9
+ import { makeNumberSquare, parseNumberSquare } from '../notationUtil';
10
+
11
+ // Olny supports standard shogi no variants
8
12
 
9
13
  //
10
14
  // CSA HEADER
@@ -14,7 +18,7 @@ export enum InvalidCsa {
14
18
  CSA = 'ERR_CSA',
15
19
  Board = 'ERR_BOARD',
16
20
  Handicap = 'ERR_HANDICAP',
17
- Pockets = 'ERR_POCKETS',
21
+ Hands = 'ERR_HANDS',
18
22
  AdditionalInfo = 'ERR_ADDITIONAL',
19
23
  }
20
24
 
@@ -24,8 +28,8 @@ export class CsaError extends Error {}
24
28
  export function makeCsaHeader(setup: Setup): string {
25
29
  return [
26
30
  makeCsaBoard(setup.board),
27
- makeCsaPocket(setup.pockets.sente, 'P+'),
28
- makeCsaPocket(setup.pockets.gote, 'P-'),
31
+ makeCsaHand(setup.hands.sente, 'P+'),
32
+ makeCsaHand(setup.hands.gote, 'P-'),
29
33
  setup.turn === 'gote' ? '-' : '+',
30
34
  ]
31
35
  .filter(p => p.length > 0)
@@ -50,15 +54,16 @@ export function makeCsaBoard(board: Board): string {
50
54
  return csaBoard;
51
55
  }
52
56
 
53
- export function makeCsaPocket(material: MaterialSide, prefix: string): string {
54
- if (material.isEmpty()) return '';
57
+ export function makeCsaHand(hand: Hand, prefix: string): string {
58
+ if (hand.isEmpty()) return '';
55
59
  return (
56
60
  prefix +
57
- POCKET_ROLES.map(role => {
58
- const r = roleToCsa(role);
59
- const n = material[role];
60
- return ('00' + r).repeat(Math.min(n, 18));
61
- })
61
+ handRoles('shogi')
62
+ .map(role => {
63
+ const r = roleToCsa(role);
64
+ const n = hand[role];
65
+ return ('00' + r).repeat(Math.min(n, 18));
66
+ })
62
67
  .filter(p => p.length > 0)
63
68
  .join('')
64
69
  );
@@ -75,7 +80,7 @@ export function parseCsaHeader(csa: string): Result<Setup, CsaError> {
75
80
  return baseBoard.chain(board => {
76
81
  const setup = {
77
82
  board: board,
78
- pockets: Material.empty(),
83
+ hands: Hands.empty(),
79
84
  turn: turn,
80
85
  fullmoves: 1,
81
86
  };
@@ -90,7 +95,7 @@ export function parseCsaHandicap(handicap: string): Result<Board, CsaError> {
90
95
  const splitted = handicap.substring(2).match(/.{4}/g) || [];
91
96
  const intitalBoard = Board.default();
92
97
  for (const s of splitted) {
93
- const sq = parseCsaSquare(s.substring(0, 2));
98
+ const sq = parseNumberSquare(s.substring(0, 2));
94
99
  if (defined(sq)) {
95
100
  intitalBoard.take(sq);
96
101
  } else {
@@ -114,7 +119,7 @@ function parseCsaBoard(csaBoard: string[]): Result<Board, CsaError> {
114
119
  else {
115
120
  if (file >= 9 || rank < 0) return Result.err(new CsaError(InvalidCsa.Board));
116
121
  const role = csaToRole(s.substring(1));
117
- if (defined(role)) {
122
+ if (defined(role) && allRoles('shogi').includes(role)) {
118
123
  const square = file + rank * 9;
119
124
  const piece = { role: role, color: (s.startsWith('-') ? 'gote' : 'sente') as Color };
120
125
  board.set(square, piece);
@@ -131,13 +136,13 @@ function parseAdditions(initialSetup: Setup, additions: string[]): Result<Setup,
131
136
  for (const line of additions) {
132
137
  const color: Color = line[1] === '+' ? 'sente' : 'gote';
133
138
  for (const sp of line.substring(2).match(/.{4}/g) || []) {
134
- const sq = parseCsaSquare(sp.substring(0, 2));
139
+ const sqString = sp.substring(0, 2);
140
+ const sq = parseNumberSquare(sqString);
135
141
  const role = csaToRole(sp.substring(2, 4));
136
- if (defined(sq) && defined(role)) {
137
- if (sq === 0) {
138
- if (!(POCKET_ROLES as ReadonlyArray<string>).includes(role))
139
- return Result.err(new CsaError(InvalidCsa.Pockets));
140
- initialSetup.pockets[color][role as PocketRole]++;
142
+ if ((defined(sq) || sqString === '00') && defined(role)) {
143
+ if (!defined(sq)) {
144
+ if (!handRoles('shogi').includes(role)) return Result.err(new CsaError(InvalidCsa.Hands));
145
+ initialSetup.hands[color][role]++;
141
146
  } else {
142
147
  initialSetup.board.set(sq, { role: role, color: color });
143
148
  }
@@ -153,6 +158,14 @@ export function parseTags(csa: string): [string, string][] {
153
158
  .map(l => l.substring(1).split(/:(.*)/, 2) as [string, string]);
154
159
  }
155
160
 
161
+ export function normalizedCsaLines(csa: string): string[] {
162
+ return csa
163
+ .replace(/,/g, '\n')
164
+ .split(/[\r\n]+/)
165
+ .map(l => l.trim())
166
+ .filter(l => l);
167
+ }
168
+
156
169
  //
157
170
  // CSA MOVES
158
171
  //
@@ -166,16 +179,16 @@ export function parseCsaMove(pos: Position, csaMove: string): Move | undefined {
166
179
  const match = csaMove.match(/(?:[\+-])?00([1-9][1-9])(HI|KA|KI|GI|KE|KY|FU)/);
167
180
  if (!match) return;
168
181
  const drop = {
169
- role: csaToRole(match[2]) as PocketRole,
170
- to: parseCsaSquare(match[1])!,
182
+ role: csaToRole(match[2])!,
183
+ to: parseNumberSquare(match[1])!,
171
184
  };
172
185
  return drop;
173
186
  }
174
187
  const role = csaToRole(match[3])!;
175
- const orig = parseCsaSquare(match[1])!;
188
+ const orig = parseNumberSquare(match[1])!;
176
189
  return {
177
190
  from: orig,
178
- to: parseCsaSquare(match[2])!,
191
+ to: parseNumberSquare(match[2])!,
179
192
  promotion: pos.board.get(orig)?.role !== role,
180
193
  };
181
194
  }
@@ -193,30 +206,16 @@ export function parseCsaMoves(pos: Position, csaMoves: string[]): Move[] {
193
206
  }
194
207
 
195
208
  // Making CSA formatted moves
196
- export function makeCsaMove(pos: Position, move: Move): string {
209
+ export function makeCsaMove(pos: Position, move: Move): string | undefined {
197
210
  if (isDrop(move)) {
198
- return '00' + makeCsaSquare(move.to) + roleToCsa(move.role);
211
+ return '00' + makeNumberSquare(move.to) + roleToCsa(move.role);
199
212
  } else {
200
213
  const role = pos.board.getRole(move.from);
201
- if (!role) return '%ERROR';
202
- return makeCsaSquare(move.from) + makeCsaSquare(move.to) + roleToCsa(move.promotion ? promote(role) : role);
203
- }
204
- }
205
-
206
- export function makeCsaVariation(pos: Position, variation: Move[]): string {
207
- pos = pos.clone();
208
- const line = [];
209
- for (const m of variation) {
210
- line.push((pos.turn === 'sente' ? '+' : '-') + makeCsaMove(pos, m));
211
- pos.play(m);
214
+ if (!role) return undefined;
215
+ return (
216
+ makeNumberSquare(move.from) +
217
+ makeNumberSquare(move.to) +
218
+ roleToCsa(move.promotion ? promote('shogi')(role) : role)
219
+ );
212
220
  }
213
- return line.join('\n');
214
- }
215
-
216
- export function normalizedCsaLines(csa: string): string[] {
217
- return csa
218
- .replace(/,/g, '\n')
219
- .split(/[\r\n]+/)
220
- .map(l => l.trim())
221
- .filter(l => l);
222
221
  }
@@ -0,0 +1,74 @@
1
+ import { roleTo2Kanji, squareFile, squareRank } from '../util';
2
+ import { Position } from '../shogi';
3
+ import { Move, isDrop, Square, Piece } from '../types';
4
+ import { pieceCanPromote } from '../variantUtil';
5
+ import { makeJapaneseSquare, piecesAiming } from './notationUtil';
6
+ import { SquareSet } from '../squareSet';
7
+
8
+ // 7六歩
9
+ export function makeJapaneseMove(pos: Position, move: Move, lastDest?: Square): string | undefined {
10
+ if (isDrop(move)) {
11
+ const ambStr = piecesAiming(pos, { role: move.role, color: pos.turn }, move.to).isEmpty() ? '' : '打';
12
+ return `${makeJapaneseSquare(move.to)}${roleTo2Kanji(move.role).toUpperCase()}${ambStr}`;
13
+ } else {
14
+ const piece = pos.board.get(move.from);
15
+ if (piece) {
16
+ const destStr = lastDest === move.to ? '同 ' : makeJapaneseSquare(move.to);
17
+ const roleStr = roleTo2Kanji(piece.role).toUpperCase();
18
+ const ambPieces = piecesAiming(pos, piece, move.to).without(move.from);
19
+ const ambStr = ambPieces.isEmpty() ? '' : disambiguate(piece, move.from, move.to, ambPieces);
20
+ const promStr = move.promotion ? '成' : pieceCanPromote(pos.rules)(piece, move.from, move.to) ? '不成' : '';
21
+ return `${destStr}${roleStr}${ambStr}${promStr}`;
22
+ } else return undefined;
23
+ }
24
+ }
25
+
26
+ function disambiguate(piece: Piece, orig: Square, dest: Square, others: SquareSet): string {
27
+ const myRank = squareRank(orig);
28
+ const myFile = squareFile(orig);
29
+
30
+ const destRank = squareRank(dest);
31
+ const destFile = squareFile(dest);
32
+
33
+ const movingUp = myRank < destRank;
34
+ const movingDown = myRank > destRank;
35
+
36
+ // special case if gold/silver like piece is moving directly forward
37
+ if (
38
+ myFile === destFile &&
39
+ (piece.color === 'sente') === movingUp &&
40
+ ['gold', 'silver', 'promotedlance', 'promotedknight', 'promotedsilver', 'tokin'].includes(piece.role)
41
+ )
42
+ return '直';
43
+
44
+ // is this the only piece moving in certain vertical direction (up, down, horizontally)
45
+ if (![...others].map(squareRank).some(r => r > destRank === movingDown && r < destRank === movingUp))
46
+ return verticalDisambiguation(piece, movingUp, movingDown);
47
+
48
+ const othersFiles = [...others].map(squareFile);
49
+ const rightest = othersFiles.reduce((prev, cur) => (prev > cur ? prev : cur));
50
+ const leftest = othersFiles.reduce((prev, cur) => (prev < cur ? prev : cur));
51
+
52
+ // is this piece positioned most on one side, not in the middle
53
+ if (rightest < myFile || leftest > myFile || (others.size() === 2 && rightest > myFile && leftest < myFile))
54
+ return sideDisambiguation(piece, rightest < squareFile(orig), leftest > squareFile(orig));
55
+
56
+ return (
57
+ sideDisambiguation(piece, rightest <= squareFile(orig), leftest >= squareFile(orig)) +
58
+ verticalDisambiguation(piece, movingUp, movingDown)
59
+ );
60
+ }
61
+
62
+ function verticalDisambiguation(piece: Piece, up: Boolean, down: Boolean): string {
63
+ return up === down
64
+ ? '寄'
65
+ : (piece.color === 'sente') === up
66
+ ? ['horse', 'dragon'].includes(piece.role)
67
+ ? '行'
68
+ : '上'
69
+ : '引';
70
+ }
71
+
72
+ function sideDisambiguation(piece: Piece, right: Boolean, left: Boolean): string {
73
+ return !left && !right ? '中' : (piece.color === 'sente') === right ? '右' : '左';
74
+ }
@@ -1,20 +1,21 @@
1
1
  import { Result } from '@badrap/result';
2
- import { Board } from './board';
3
- import { INITIAL_FEN, makeFen, parseFen } from './fen';
2
+ import { Board } from '../../board';
3
+ import { INITIAL_SFEN, makeSfen, parseSfen } from '../../sfen';
4
4
  import { handicapNameToSfen, sfenToHandicapName } from './kifHandicaps';
5
- import { Material, MaterialSide, Setup } from './setup';
6
- import { Position } from './shogi';
7
- import { Color, isDrop, Move, PocketRole, POCKET_ROLES, Square } from './types';
8
- import { defined, kanjiToRole, promote, roleTo1Kanji, roleTo2Kanji } from './util';
5
+ import { Setup } from '../../setup';
6
+ import { Position } from '../../shogi';
7
+ import { Color, isDrop, Move, Square } from '../../types';
8
+ import { defined, kanjiToRole, roleTo1Kanji, roleTo2Kanji } from '../../util';
9
9
 
10
+ import { Hand, Hands } from '../../hand';
11
+ import { allRoles, handRoles, promote } from '../../variantUtil';
10
12
  import {
11
13
  kanjiToNumber,
12
- kifDestSquare,
13
- kifOrigSquare,
14
- normalizedKifLines,
14
+ makeJapaneseSquare,
15
+ makeNumberSquare,
15
16
  numberToKanji,
16
- parseKifSquare,
17
- } from './kifUtil';
17
+ parseJapaneseSquare,
18
+ } from '../notationUtil';
18
19
 
19
20
  //
20
21
  // KIF HEADER
@@ -24,34 +25,37 @@ export enum InvalidKif {
24
25
  Kif = 'ERR_KIF',
25
26
  Board = 'ERR_BOARD',
26
27
  Handicap = 'ERR_HANDICAP',
27
- Pockets = 'ERR_POCKETS',
28
+ Hands = 'ERR_HANDS',
28
29
  }
29
30
 
30
31
  export class KifError extends Error {}
31
32
 
32
33
  // Export
33
34
  export function makeKifHeader(setup: Setup): string {
34
- const handicap = sfenToHandicapName(makeFen(setup, { epd: true }));
35
+ const handicap = sfenToHandicapName(makeSfen(setup, { epd: true }));
35
36
  if (defined(handicap)) return '手合割:' + handicap;
36
37
  return makeKifPositionHeader(setup);
37
38
  }
38
39
 
39
40
  export function makeKifPositionHeader(setup: Setup): string {
40
41
  return [
41
- '後手の持駒:' + makeKifPocket(setup.pockets.gote),
42
+ '後手の持駒:' + makeKifHand(setup.hands.gote),
42
43
  makeKifBoard(setup.board),
43
- '先手の持駒:' + makeKifPocket(setup.pockets.sente),
44
+ '先手の持駒:' + makeKifHand(setup.hands.sente),
44
45
  ...(setup.turn === 'gote' ? ['後手番'] : []),
45
46
  ].join('\n');
46
47
  }
47
48
 
48
49
  export function makeKifBoard(board: Board): string {
49
- let kifBoard = ' 9 8 7 6 5 4 3 2 1\n+---------------------------+\n';
50
- for (let rank = 8; rank >= 0; rank--) {
51
- for (let file = 0; file < 9; file++) {
50
+ const kifFiles = ' 9 8 7 6 5 4 3 2 '.slice(-(board.numberOfFiles * 2));
51
+ const separator = '+' + '-'.repeat(board.numberOfFiles * 3) + '+';
52
+ const offset = 9 - board.numberOfFiles;
53
+ let kifBoard = ' ' + kifFiles + `\n${separator}\n`;
54
+ for (let rank = 8; rank >= 9 - board.numberOfRanks; rank--) {
55
+ for (let file = offset; file < 9; file++) {
52
56
  const square = file + rank * 9;
53
57
  const piece = board.get(square);
54
- if (file === 0) {
58
+ if (file === offset) {
55
59
  kifBoard += '|';
56
60
  }
57
61
  if (!piece) kifBoard += ' ・';
@@ -64,17 +68,18 @@ export function makeKifBoard(board: Board): string {
64
68
  }
65
69
  }
66
70
  }
67
- kifBoard += '+---------------------------+';
71
+ kifBoard += separator;
68
72
  return kifBoard;
69
73
  }
70
74
 
71
- export function makeKifPocket(material: MaterialSide): string {
72
- if (material.isEmpty()) return 'なし';
73
- return POCKET_ROLES.map(role => {
74
- const r = roleTo1Kanji(role);
75
- const n = material[role];
76
- return n > 1 ? r + numberToKanji(n) : n === 1 ? r : '';
77
- })
75
+ export function makeKifHand(hand: Hand): string {
76
+ if (hand.isEmpty()) return 'なし';
77
+ return handRoles('shogi')
78
+ .map(role => {
79
+ const r = roleTo1Kanji(role);
80
+ const n = hand[role];
81
+ return n > 1 ? r + numberToKanji(n) : n === 1 ? r : '';
82
+ })
78
83
  .filter(p => p.length > 0)
79
84
  .join(' ');
80
85
  }
@@ -83,12 +88,12 @@ export function makeKifPocket(material: MaterialSide): string {
83
88
  export function parseKifHeader(kif: string): Result<Setup, KifError> {
84
89
  const lines = normalizedKifLines(kif);
85
90
  const handicap = lines.find(l => l.startsWith('手合割:'));
86
- const hSfen = defined(handicap) ? handicapNameToSfen(handicap.split(':')[1]) : INITIAL_FEN;
91
+ const hSfen = defined(handicap) ? handicapNameToSfen(handicap.split(':')[1]) : INITIAL_SFEN;
87
92
  return parseKifPositionHeader(kif).unwrap(
88
93
  kifBoard => Result.ok(kifBoard),
89
94
  () => {
90
95
  if (!defined(hSfen)) return Result.err(new KifError(InvalidKif.Handicap));
91
- return parseFen(hSfen);
96
+ return parseSfen(hSfen);
92
97
  }
93
98
  );
94
99
  }
@@ -96,25 +101,21 @@ export function parseKifHeader(kif: string): Result<Setup, KifError> {
96
101
  export function parseKifPositionHeader(kif: string): Result<Setup, KifError> {
97
102
  const lines = normalizedKifLines(kif);
98
103
 
99
- const gotePocketStr = lines.find(l => l.startsWith('後手の持駒:'));
100
- const sentePocketStr = lines.find(l => l.startsWith('先手の持駒:'));
104
+ const goteHandStr = lines.find(l => l.startsWith('後手の持駒:'));
105
+ const senteHandStr = lines.find(l => l.startsWith('先手の持駒:'));
101
106
  const turn = lines.some(l => l.startsWith('後手番')) ? 'gote' : 'sente';
102
107
 
103
108
  const board: Result<Board, KifError> = parseKifBoard(kif);
104
109
 
105
- const gotePocket = defined(gotePocketStr)
106
- ? parseKifHand(gotePocketStr.split(':')[1])
107
- : Result.ok(MaterialSide.empty());
108
- const sentePocket = defined(sentePocketStr)
109
- ? parseKifHand(sentePocketStr.split(':')[1])
110
- : Result.ok(MaterialSide.empty());
110
+ const goteHand = defined(goteHandStr) ? parseKifHand(goteHandStr.split(':')[1]) : Result.ok(Hand.empty());
111
+ const senteHand = defined(senteHandStr) ? parseKifHand(senteHandStr.split(':')[1]) : Result.ok(Hand.empty());
111
112
 
112
113
  return board.chain(board =>
113
- gotePocket.chain(gPocket =>
114
- sentePocket.map(sPocket => {
114
+ goteHand.chain(gHand =>
115
+ senteHand.map(sHand => {
115
116
  return {
116
117
  board,
117
- pockets: new Material(gPocket, sPocket),
118
+ hands: new Hands(gHand, sHand),
118
119
  turn,
119
120
  fullmoves: 1,
120
121
  };
@@ -125,13 +126,19 @@ export function parseKifPositionHeader(kif: string): Result<Setup, KifError> {
125
126
 
126
127
  export function parseKifBoard(kifBoard: string): Result<Board, KifError> {
127
128
  const lines = normalizedKifLines(kifBoard).filter(l => l.startsWith('|'));
128
- if (lines.length !== 9) return Result.err(new KifError(InvalidKif.Board));
129
+ if (lines.length === 0) return Result.err(new KifError(InvalidKif.Board));
129
130
  const board = Board.empty();
130
- let file = 0;
131
+
132
+ // assuming square board
133
+ board.numberOfRanks = lines.length;
134
+ board.numberOfFiles = lines.length;
135
+
136
+ const offset = 9 - lines.length;
137
+ let file = offset;
131
138
  let rank = 9;
132
139
 
133
140
  for (const l of lines) {
134
- file = 0;
141
+ file = offset;
135
142
  rank--;
136
143
  let gote = false;
137
144
  let prom = false;
@@ -149,9 +156,9 @@ export function parseKifBoard(kifBoard: string): Result<Board, KifError> {
149
156
  default:
150
157
  if (file > 9 || rank < 0) return Result.err(new KifError(InvalidKif.Board));
151
158
  const role = kanjiToRole(c);
152
- if (defined(role)) {
159
+ if (defined(role) && allRoles('shogi').includes(role)) {
153
160
  const square = file + rank * 9;
154
- const piece = { role: prom ? promote(role) : role, color: (gote ? 'gote' : 'sente') as Color };
161
+ const piece = { role: prom ? promote('shogi')(role) : role, color: (gote ? 'gote' : 'sente') as Color };
155
162
  board.set(square, piece);
156
163
  prom = false;
157
164
  gote = false;
@@ -160,27 +167,27 @@ export function parseKifBoard(kifBoard: string): Result<Board, KifError> {
160
167
  }
161
168
  }
162
169
  }
163
- if (rank !== 0 || file !== 9) return Result.err(new KifError(InvalidKif.Board));
170
+ if (rank !== 9 - lines.length || file !== 9) return Result.err(new KifError(InvalidKif.Board));
164
171
  return Result.ok(board);
165
172
  }
166
173
 
167
- export function parseKifHand(pocketPart: string): Result<MaterialSide, KifError> {
168
- const pockets = MaterialSide.empty();
169
- const pieces = pocketPart.replace(/ /g, ' ').trim().split(' ');
174
+ export function parseKifHand(handPart: string): Result<Hand, KifError> {
175
+ const hand = Hand.empty();
176
+ const pieces = handPart.replace(/ /g, ' ').trim().split(' ');
170
177
 
171
- if (pocketPart.includes('なし')) return Result.ok(pockets);
178
+ if (handPart.includes('なし')) return Result.ok(hand);
172
179
  for (const piece of pieces) {
173
180
  for (let i = 0; i < piece.length; i++) {
174
181
  const role = kanjiToRole(piece[i++]);
175
- if (!role) return Result.err(new KifError(InvalidKif.Pockets));
182
+ if (!role || !handRoles('shogi').includes(role)) return Result.err(new KifError(InvalidKif.Hands));
176
183
  let countStr = '';
177
184
  while (i < piece.length && ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'].includes(piece[i]))
178
185
  countStr += piece[i++];
179
186
  const count = kanjiToNumber(countStr) || 1;
180
- pockets[role as PocketRole] += count;
187
+ hand[role] += count;
181
188
  }
182
189
  }
183
- return Result.ok(pockets);
190
+ return Result.ok(hand);
184
191
  }
185
192
 
186
193
  export function parseTags(kif: string): [string, string][] {
@@ -189,6 +196,15 @@ export function parseTags(kif: string): [string, string][] {
189
196
  .map(l => l.replace(':', ':').split(/:(.*)/, 2) as [string, string]);
190
197
  }
191
198
 
199
+ export function normalizedKifLines(kif: string): string[] {
200
+ return kif
201
+ .replace(/:/g, ':')
202
+ .replace(/ /g, ' ') // full-width space to normal space
203
+ .split(/[\r\n]+/)
204
+ .map(l => l.trim())
205
+ .filter(l => l);
206
+ }
207
+
192
208
  //
193
209
  // KIF MOVES
194
210
  //
@@ -204,15 +220,15 @@ export function parseKifMove(kifMove: string, lastDest: Square | undefined = und
204
220
  const match = kifMove.match(/((?:[123456789][一二三四五六七八九]|同\s?))(飛|角|金|銀|桂|香|歩)打/);
205
221
  if (!match) return;
206
222
  const move = {
207
- role: kanjiToRole(match[2]) as PocketRole,
208
- to: parseKifSquare(match[1]) ?? lastDest!,
223
+ role: kanjiToRole(match[2])!,
224
+ to: parseJapaneseSquare(match[1]) ?? lastDest!,
209
225
  };
210
226
  return move;
211
227
  }
212
228
 
213
229
  return {
214
- from: parseKifSquare(match[4])!,
215
- to: parseKifSquare(match[1]) ?? lastDest!,
230
+ from: parseJapaneseSquare(match[4])!,
231
+ to: parseJapaneseSquare(match[1]) ?? lastDest!,
216
232
  promotion: !!match[3],
217
233
  };
218
234
  }
@@ -229,30 +245,13 @@ export function parseKifMoves(kifMoves: string[], lastDest: Square | undefined =
229
245
  }
230
246
 
231
247
  // Making kif formatted moves
232
- export function makeKifMove(pos: Position, move: Move, same = false): string {
233
- const moveDest = same ? '同 ' : kifDestSquare(move.to);
248
+ export function makeKifMove(pos: Position, move: Move, lastDest?: Square): string | undefined {
249
+ const moveDest = lastDest === move.to ? '同 ' : makeJapaneseSquare(move.to);
234
250
  if (isDrop(move)) {
235
251
  return moveDest + roleTo1Kanji(move.role) + '打';
236
252
  } else {
237
253
  const role = pos.board.getRole(move.from);
238
- if (!role) return '反則';
239
- return moveDest + roleTo2Kanji(role) + (move.promotion ? '成' : '') + '(' + kifOrigSquare(move.from) + ')';
240
- }
241
- }
242
-
243
- export function makeKifVariation(
244
- pos: Position,
245
- variation: Move[],
246
- lastDest: Square | undefined = undefined,
247
- startTurn = 1
248
- ): string {
249
- pos = pos.clone();
250
- const line = [];
251
- const padding = (startTurn + variation.length - 1).toString().length;
252
- for (const m of variation) {
253
- line.push((startTurn++).toString().padStart(padding) + ' ' + makeKifMove(pos, m, m.to === lastDest));
254
- pos.play(m);
255
- lastDest = m.to;
254
+ if (!role) return undefined;
255
+ return moveDest + roleTo2Kanji(role) + (move.promotion ? '成' : '') + '(' + makeNumberSquare(move.from) + ')';
256
256
  }
257
- return line.join('\n');
258
257
  }
@@ -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
  }
@@ -0,0 +1,24 @@
1
+ import { roleTo2Kanji } from '../util';
2
+ import { Position } from '../shogi';
3
+ import { Move, isDrop, Square } from '../types';
4
+ import { pieceCanPromote } from '../variantUtil';
5
+ import { makeNumberSquare, piecesAiming } from './notationUtil';
6
+
7
+ // 歩-76
8
+ export function makeKitaoKawasakiMove(pos: Position, move: Move, lastDest?: Square): string | undefined {
9
+ if (isDrop(move)) {
10
+ return roleTo2Kanji(move.role).toUpperCase() + '*' + makeNumberSquare(move.to);
11
+ } else {
12
+ const piece = pos.board.get(move.from);
13
+ if (piece) {
14
+ const roleStr = roleTo2Kanji(piece.role).toUpperCase();
15
+ const ambStr = piecesAiming(pos, piece, move.to).without(move.from).isEmpty()
16
+ ? ''
17
+ : `(${makeNumberSquare(move.from)})`;
18
+ const actionStr = pos.board.has(move.to) ? 'x' : '-';
19
+ const destStr = lastDest === move.to ? '' : makeNumberSquare(move.to);
20
+ const promStr = move.promotion ? '+' : pieceCanPromote(pos.rules)(piece, move.from, move.to) ? '=' : '';
21
+ return `${roleStr}${ambStr}${actionStr}${destStr}${promStr}`;
22
+ } else return undefined;
23
+ }
24
+ }