Draft Allmate variant
[vchess.git] / client / src / variants / Baroque.js
CommitLineData
0c3fe8a6
BA
1import { ChessRules, PiPo, Move } from "@/base_rules";
2import { ArrayFun } from "@/utils/array";
3import { randInt } from "@/utils/alea";
4
6808d7a1
BA
5export const VariantRules = class BaroqueRules extends ChessRules {
6 static get HasFlags() {
7 return false;
8 }
dac39588 9
6808d7a1
BA
10 static get HasEnpassant() {
11 return false;
12 }
dac39588 13
6808d7a1
BA
14 static getPpath(b) {
15 if (b[1] == "m")
16 //'m' for Immobilizer (I is too similar to 1)
dac39588
BA
17 return "Baroque/" + b;
18 return b; //usual piece
19 }
20
6808d7a1 21 static get PIECES() {
dac39588
BA
22 return ChessRules.PIECES.concat([V.IMMOBILIZER]);
23 }
24
25 // No castling, but checks, so keep track of kings
6808d7a1
BA
26 setOtherVariables(fen) {
27 this.kingPos = { w: [-1, -1], b: [-1, -1] };
dac39588
BA
28 const fenParts = fen.split(" ");
29 const position = fenParts[0].split("/");
6808d7a1 30 for (let i = 0; i < position.length; i++) {
dac39588 31 let k = 0;
6808d7a1
BA
32 for (let j = 0; j < position[i].length; j++) {
33 switch (position[i].charAt(j)) {
34 case "k":
35 this.kingPos["b"] = [i, k];
dac39588 36 break;
6808d7a1
BA
37 case "K":
38 this.kingPos["w"] = [i, k];
dac39588 39 break;
6808d7a1
BA
40 default: {
41 const num = parseInt(position[i].charAt(j));
42 if (!isNaN(num)) k += num - 1;
43 }
dac39588
BA
44 }
45 k++;
46 }
47 }
48 }
49
6808d7a1
BA
50 static get IMMOBILIZER() {
51 return "m";
52 }
dac39588
BA
53 // Although other pieces keep their names here for coding simplicity,
54 // keep in mind that:
55 // - a "rook" is a coordinator, capturing by coordinating with the king
56 // - a "knight" is a long-leaper, capturing as in draughts
57 // - a "bishop" is a chameleon, capturing as its prey
58 // - a "queen" is a withdrawer, capturing by moving away from pieces
59
60 // Is piece on square (x,y) immobilized?
6808d7a1
BA
61 isImmobilized([x, y]) {
62 const piece = this.getPiece(x, y);
63 const color = this.getColor(x, y);
dac39588
BA
64 const oppCol = V.GetOppCol(color);
65 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
6808d7a1
BA
66 for (let step of adjacentSteps) {
67 const [i, j] = [x + step[0], y + step[1]];
68 if (
69 V.OnBoard(i, j) &&
70 this.board[i][j] != V.EMPTY &&
71 this.getColor(i, j) == oppCol
72 ) {
73 const oppPiece = this.getPiece(i, j);
74 if (oppPiece == V.IMMOBILIZER) {
dac39588 75 // Moving is impossible only if this immobilizer is not neutralized
6808d7a1
BA
76 for (let step2 of adjacentSteps) {
77 const [i2, j2] = [i + step2[0], j + step2[1]];
78 if (i2 == x && j2 == y) continue; //skip initial piece!
79 if (
80 V.OnBoard(i2, j2) &&
81 this.board[i2][j2] != V.EMPTY &&
82 this.getColor(i2, j2) == color
83 ) {
84 if ([V.BISHOP, V.IMMOBILIZER].includes(this.getPiece(i2, j2)))
dac39588
BA
85 return false;
86 }
87 }
88 return true; //immobilizer isn't neutralized
89 }
90 // Chameleons can't be immobilized twice, because there is only one immobilizer
6808d7a1 91 if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER) return true;
dac39588
BA
92 }
93 }
94 return false;
95 }
96
6808d7a1 97 getPotentialMovesFrom([x, y]) {
dac39588 98 // Pre-check: is thing on this square immobilized?
6808d7a1
BA
99 if (this.isImmobilized([x, y])) return [];
100 switch (this.getPiece(x, y)) {
dac39588 101 case V.IMMOBILIZER:
6808d7a1 102 return this.getPotentialImmobilizerMoves([x, y]);
dac39588 103 default:
6808d7a1 104 return super.getPotentialMovesFrom([x, y]);
dac39588
BA
105 }
106 }
107
6808d7a1
BA
108 getSlideNJumpMoves([x, y], steps, oneStep) {
109 const piece = this.getPiece(x, y);
dac39588 110 let moves = [];
6808d7a1 111 outerLoop: for (let step of steps) {
dac39588
BA
112 let i = x + step[0];
113 let j = y + step[1];
6808d7a1
BA
114 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
115 moves.push(this.getBasicMove([x, y], [i, j]));
116 if (oneStep !== undefined) continue outerLoop;
dac39588
BA
117 i += step[0];
118 j += step[1];
119 }
120 // Only king can take on occupied square:
6808d7a1
BA
121 if (piece == V.KING && V.OnBoard(i, j) && this.canTake([x, y], [i, j]))
122 moves.push(this.getBasicMove([x, y], [i, j]));
dac39588
BA
123 }
124 return moves;
125 }
126
127 // Modify capturing moves among listed pawn moves
6808d7a1 128 addPawnCaptures(moves, byChameleon) {
dac39588
BA
129 const steps = V.steps[V.ROOK];
130 const color = this.turn;
131 const oppCol = V.GetOppCol(color);
132 moves.forEach(m => {
6808d7a1 133 if (!!byChameleon && m.start.x != m.end.x && m.start.y != m.end.y) return; //chameleon not moving as pawn
dac39588 134 // Try capturing in every direction
6808d7a1
BA
135 for (let step of steps) {
136 const sq2 = [m.end.x + 2 * step[0], m.end.y + 2 * step[1]];
137 if (
138 V.OnBoard(sq2[0], sq2[1]) &&
139 this.board[sq2[0]][sq2[1]] != V.EMPTY &&
140 this.getColor(sq2[0], sq2[1]) == color
141 ) {
dac39588 142 // Potential capture
6808d7a1
BA
143 const sq1 = [m.end.x + step[0], m.end.y + step[1]];
144 if (
145 this.board[sq1[0]][sq1[1]] != V.EMPTY &&
146 this.getColor(sq1[0], sq1[1]) == oppCol
147 ) {
148 const piece1 = this.getPiece(sq1[0], sq1[1]);
149 if (!byChameleon || piece1 == V.PAWN) {
150 m.vanish.push(
151 new PiPo({
152 x: sq1[0],
153 y: sq1[1],
154 c: oppCol,
155 p: piece1
156 })
157 );
dac39588
BA
158 }
159 }
160 }
161 }
162 });
163 }
164
165 // "Pincer"
6808d7a1
BA
166 getPotentialPawnMoves([x, y]) {
167 let moves = super.getPotentialRookMoves([x, y]);
dac39588
BA
168 this.addPawnCaptures(moves);
169 return moves;
170 }
171
6808d7a1 172 addRookCaptures(moves, byChameleon) {
dac39588
BA
173 const color = this.turn;
174 const oppCol = V.GetOppCol(color);
175 const kp = this.kingPos[color];
176 moves.forEach(m => {
177 // Check piece-king rectangle (if any) corners for enemy pieces
6808d7a1 178 if (m.end.x == kp[0] || m.end.y == kp[1]) return; //"flat rectangle"
dac39588
BA
179 const corner1 = [m.end.x, kp[1]];
180 const corner2 = [kp[0], m.end.y];
6808d7a1
BA
181 for (let [i, j] of [corner1, corner2]) {
182 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == oppCol) {
183 const piece = this.getPiece(i, j);
184 if (!byChameleon || piece == V.ROOK) {
185 m.vanish.push(
186 new PiPo({
187 x: i,
188 y: j,
189 p: piece,
190 c: oppCol
191 })
192 );
dac39588
BA
193 }
194 }
195 }
196 });
197 }
198
199 // Coordinator
6808d7a1 200 getPotentialRookMoves(sq) {
dac39588
BA
201 let moves = super.getPotentialQueenMoves(sq);
202 this.addRookCaptures(moves);
203 return moves;
204 }
205
6808d7a1 206 getKnightCaptures(startSquare, byChameleon) {
dac39588
BA
207 // Look in every direction for captures
208 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
209 const color = this.turn;
210 const oppCol = V.GetOppCol(color);
211 let moves = [];
6808d7a1
BA
212 const [x, y] = [startSquare[0], startSquare[1]];
213 const piece = this.getPiece(x, y); //might be a chameleon!
214 outerLoop: for (let step of steps) {
215 let [i, j] = [x + step[0], y + step[1]];
216 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
dac39588
BA
217 i += step[0];
218 j += step[1];
219 }
6808d7a1
BA
220 if (
221 !V.OnBoard(i, j) ||
222 this.getColor(i, j) == color ||
223 (!!byChameleon && this.getPiece(i, j) != V.KNIGHT)
224 ) {
dac39588
BA
225 continue;
226 }
227 // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
228 // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
229 // add move until cur square; if cur is occupied then stop if !!byChameleon and
230 // the square not occupied by a leaper.
6808d7a1
BA
231 let last = [i, j];
232 let cur = [i + step[0], j + step[1]];
233 let vanished = [new PiPo({ x: x, y: y, c: color, p: piece })];
234 while (V.OnBoard(cur[0], cur[1])) {
235 if (this.board[last[0]][last[1]] != V.EMPTY) {
236 const oppPiece = this.getPiece(last[0], last[1]);
237 if (!!byChameleon && oppPiece != V.KNIGHT) continue outerLoop;
dac39588 238 // Something to eat:
6808d7a1
BA
239 vanished.push(
240 new PiPo({ x: last[0], y: last[1], c: oppCol, p: oppPiece })
241 );
dac39588 242 }
6808d7a1
BA
243 if (this.board[cur[0]][cur[1]] != V.EMPTY) {
244 if (
245 this.getColor(cur[0], cur[1]) == color ||
246 this.board[last[0]][last[1]] != V.EMPTY
247 ) {
248 //TODO: redundant test
dac39588
BA
249 continue outerLoop;
250 }
6808d7a1
BA
251 } else {
252 moves.push(
253 new Move({
254 appear: [new PiPo({ x: cur[0], y: cur[1], c: color, p: piece })],
255 vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
256 start: { x: x, y: y },
257 end: { x: cur[0], y: cur[1] }
258 })
259 );
dac39588 260 }
6808d7a1
BA
261 last = [last[0] + step[0], last[1] + step[1]];
262 cur = [cur[0] + step[0], cur[1] + step[1]];
dac39588
BA
263 }
264 }
265 return moves;
266 }
267
268 // Long-leaper
6808d7a1 269 getPotentialKnightMoves(sq) {
dac39588
BA
270 return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
271 }
272
78d64531 273 // Chameleon
6808d7a1
BA
274 getPotentialBishopMoves([x, y]) {
275 let moves = super
276 .getPotentialQueenMoves([x, y])
277 .concat(this.getKnightCaptures([x, y], "asChameleon"));
dac39588
BA
278 // No "king capture" because king cannot remain under check
279 this.addPawnCaptures(moves, "asChameleon");
280 this.addRookCaptures(moves, "asChameleon");
281 this.addQueenCaptures(moves, "asChameleon");
282 // Post-processing: merge similar moves, concatenating vanish arrays
283 let mergedMoves = {};
284 moves.forEach(m => {
285 const key = m.end.x + V.size.x * m.end.y;
6808d7a1
BA
286 if (!mergedMoves[key]) mergedMoves[key] = m;
287 else {
288 for (let i = 1; i < m.vanish.length; i++)
dac39588
BA
289 mergedMoves[key].vanish.push(m.vanish[i]);
290 }
291 });
292 // Finally return an array
293 moves = [];
6808d7a1
BA
294 Object.keys(mergedMoves).forEach(k => {
295 moves.push(mergedMoves[k]);
296 });
dac39588
BA
297 return moves;
298 }
299
6808d7a1
BA
300 addQueenCaptures(moves, byChameleon) {
301 if (moves.length == 0) return;
302 const [x, y] = [moves[0].start.x, moves[0].start.y];
dac39588
BA
303 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
304 let capturingDirections = [];
305 const color = this.turn;
306 const oppCol = V.GetOppCol(color);
307 adjacentSteps.forEach(step => {
6808d7a1
BA
308 const [i, j] = [x + step[0], y + step[1]];
309 if (
310 V.OnBoard(i, j) &&
311 this.board[i][j] != V.EMPTY &&
312 this.getColor(i, j) == oppCol &&
313 (!byChameleon || this.getPiece(i, j) == V.QUEEN)
314 ) {
dac39588
BA
315 capturingDirections.push(step);
316 }
317 });
318 moves.forEach(m => {
319 const step = [
6808d7a1
BA
320 m.end.x != x ? (m.end.x - x) / Math.abs(m.end.x - x) : 0,
321 m.end.y != y ? (m.end.y - y) / Math.abs(m.end.y - y) : 0
dac39588
BA
322 ];
323 // NOTE: includes() and even _.isEqual() functions fail...
324 // TODO: this test should be done only once per direction
6808d7a1
BA
325 if (
326 capturingDirections.some(dir => {
327 return dir[0] == -step[0] && dir[1] == -step[1];
328 })
329 ) {
330 const [i, j] = [x - step[0], y - step[1]];
331 m.vanish.push(
332 new PiPo({
333 x: i,
334 y: j,
335 p: this.getPiece(i, j),
336 c: oppCol
337 })
338 );
dac39588
BA
339 }
340 });
341 }
342
78d64531 343 // Withdrawer
6808d7a1 344 getPotentialQueenMoves(sq) {
dac39588
BA
345 let moves = super.getPotentialQueenMoves(sq);
346 this.addQueenCaptures(moves);
347 return moves;
348 }
349
6808d7a1 350 getPotentialImmobilizerMoves(sq) {
dac39588
BA
351 // Immobilizer doesn't capture
352 return super.getPotentialQueenMoves(sq);
353 }
354
6808d7a1
BA
355 getPotentialKingMoves(sq) {
356 return this.getSlideNJumpMoves(
357 sq,
358 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
359 "oneStep"
360 );
dac39588
BA
361 }
362
363 // isAttacked() is OK because the immobilizer doesn't take
364
6808d7a1 365 isAttackedByPawn([x, y], colors) {
dac39588
BA
366 // Square (x,y) must be surroundable by two enemy pieces,
367 // and one of them at least should be a pawn (moving).
6808d7a1
BA
368 const dirs = [
369 [1, 0],
370 [0, 1]
371 ];
dac39588 372 const steps = V.steps[V.ROOK];
6808d7a1
BA
373 for (let dir of dirs) {
374 const [i1, j1] = [x - dir[0], y - dir[1]]; //"before"
375 const [i2, j2] = [x + dir[0], y + dir[1]]; //"after"
376 if (V.OnBoard(i1, j1) && V.OnBoard(i2, j2)) {
377 if (
378 (this.board[i1][j1] != V.EMPTY &&
379 colors.includes(this.getColor(i1, j1)) &&
380 this.board[i2][j2] == V.EMPTY) ||
381 (this.board[i2][j2] != V.EMPTY &&
382 colors.includes(this.getColor(i2, j2)) &&
383 this.board[i1][j1] == V.EMPTY)
384 ) {
dac39588 385 // Search a movable enemy pawn landing on the empty square
6808d7a1
BA
386 for (let step of steps) {
387 let [ii, jj] = this.board[i1][j1] == V.EMPTY ? [i1, j1] : [i2, j2];
388 let [i3, j3] = [ii + step[0], jj + step[1]];
389 while (V.OnBoard(i3, j3) && this.board[i3][j3] == V.EMPTY) {
dac39588
BA
390 i3 += step[0];
391 j3 += step[1];
392 }
6808d7a1
BA
393 if (
394 V.OnBoard(i3, j3) &&
395 colors.includes(this.getColor(i3, j3)) &&
396 this.getPiece(i3, j3) == V.PAWN &&
397 !this.isImmobilized([i3, j3])
398 ) {
dac39588
BA
399 return true;
400 }
401 }
402 }
403 }
404 }
405 return false;
406 }
407
6808d7a1 408 isAttackedByRook([x, y], colors) {
dac39588
BA
409 // King must be on same column or row,
410 // and a rook should be able to reach a capturing square
411 // colors contains only one element, giving the oppCol and thus king position
6808d7a1
BA
412 const sameRow = x == this.kingPos[colors[0]][0];
413 const sameColumn = y == this.kingPos[colors[0]][1];
414 if (sameRow || sameColumn) {
dac39588 415 // Look for the enemy rook (maximum 1)
6808d7a1
BA
416 for (let i = 0; i < V.size.x; i++) {
417 for (let j = 0; j < V.size.y; j++) {
418 if (
419 this.board[i][j] != V.EMPTY &&
420 colors.includes(this.getColor(i, j)) &&
421 this.getPiece(i, j) == V.ROOK
422 ) {
423 if (this.isImmobilized([i, j])) return false; //because only one rook
dac39588
BA
424 // Can it reach a capturing square?
425 // Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
6808d7a1
BA
426 const moves = this.getPotentialMovesFrom([i, j]);
427 for (let move of moves) {
428 if (
429 (sameRow && move.end.y == y) ||
430 (sameColumn && move.end.x == x)
431 )
dac39588
BA
432 return true;
433 }
434 }
435 }
436 }
437 }
438 return false;
439 }
440
6808d7a1 441 isAttackedByKnight([x, y], colors) {
dac39588
BA
442 // Square (x,y) must be on same line as a knight,
443 // and there must be empty square(s) behind.
444 const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
6808d7a1
BA
445 outerLoop: for (let step of steps) {
446 const [i0, j0] = [x + step[0], y + step[1]];
447 if (V.OnBoard(i0, j0) && this.board[i0][j0] == V.EMPTY) {
dac39588 448 // Try in opposite direction:
6808d7a1
BA
449 let [i, j] = [x - step[0], y - step[1]];
450 while (V.OnBoard(i, j)) {
451 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
dac39588
BA
452 i -= step[0];
453 j -= step[1];
454 }
6808d7a1
BA
455 if (V.OnBoard(i, j)) {
456 if (colors.includes(this.getColor(i, j))) {
457 if (
458 this.getPiece(i, j) == V.KNIGHT &&
459 !this.isImmobilized([i, j])
460 )
dac39588
BA
461 return true;
462 continue outerLoop;
463 }
464 // [else] Our color, could be captured *if there was an empty space*
6808d7a1 465 if (this.board[i + step[0]][j + step[1]] != V.EMPTY)
dac39588
BA
466 continue outerLoop;
467 i -= step[0];
468 j -= step[1];
469 }
470 }
471 }
472 }
473 return false;
474 }
475
6808d7a1 476 isAttackedByBishop([x, y], colors) {
dac39588
BA
477 // We cheat a little here: since this function is used exclusively for king,
478 // it's enough to check the immediate surrounding of the square.
479 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
6808d7a1
BA
480 for (let step of adjacentSteps) {
481 const [i, j] = [x + step[0], y + step[1]];
482 if (
483 V.OnBoard(i, j) &&
484 this.board[i][j] != V.EMPTY &&
485 colors.includes(this.getColor(i, j)) &&
486 this.getPiece(i, j) == V.BISHOP
487 ) {
dac39588
BA
488 return true; //bishops are never immobilized
489 }
490 }
491 return false;
492 }
493
6808d7a1 494 isAttackedByQueen([x, y], colors) {
dac39588
BA
495 // Square (x,y) must be adjacent to a queen, and the queen must have
496 // some free space in the opposite direction from (x,y)
497 const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
6808d7a1
BA
498 for (let step of adjacentSteps) {
499 const sq2 = [x + 2 * step[0], y + 2 * step[1]];
500 if (V.OnBoard(sq2[0], sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY) {
501 const sq1 = [x + step[0], y + step[1]];
502 if (
503 this.board[sq1[0]][sq1[1]] != V.EMPTY &&
504 colors.includes(this.getColor(sq1[0], sq1[1])) &&
505 this.getPiece(sq1[0], sq1[1]) == V.QUEEN &&
506 !this.isImmobilized(sq1)
507 ) {
dac39588
BA
508 return true;
509 }
510 }
511 }
512 return false;
513 }
514
6808d7a1 515 static get VALUES() {
dac39588 516 return {
6808d7a1
BA
517 p: 1,
518 r: 2,
519 n: 5,
520 b: 3,
521 q: 3,
522 m: 5,
523 k: 1000
dac39588
BA
524 };
525 }
526
6808d7a1
BA
527 static get SEARCH_DEPTH() {
528 return 2;
529 }
dac39588 530
6808d7a1
BA
531 static GenRandInitFen() {
532 let pieces = { w: new Array(8), b: new Array(8) };
dac39588 533 // Shuffle pieces on first and last rank
6808d7a1 534 for (let c of ["w", "b"]) {
dac39588
BA
535 let positions = ArrayFun.range(8);
536 // Get random squares for every piece, totally freely
537
538 let randIndex = randInt(8);
539 const bishop1Pos = positions[randIndex];
540 positions.splice(randIndex, 1);
541
542 randIndex = randInt(7);
543 const bishop2Pos = positions[randIndex];
544 positions.splice(randIndex, 1);
545
546 randIndex = randInt(6);
547 const knight1Pos = positions[randIndex];
548 positions.splice(randIndex, 1);
549
550 randIndex = randInt(5);
551 const knight2Pos = positions[randIndex];
552 positions.splice(randIndex, 1);
553
554 randIndex = randInt(4);
555 const queenPos = positions[randIndex];
556 positions.splice(randIndex, 1);
557
558 randIndex = randInt(3);
559 const kingPos = positions[randIndex];
560 positions.splice(randIndex, 1);
561
562 randIndex = randInt(2);
563 const rookPos = positions[randIndex];
564 positions.splice(randIndex, 1);
565 const immobilizerPos = positions[0];
566
6808d7a1
BA
567 pieces[c][bishop1Pos] = "b";
568 pieces[c][bishop2Pos] = "b";
569 pieces[c][knight1Pos] = "n";
570 pieces[c][knight2Pos] = "n";
571 pieces[c][queenPos] = "q";
572 pieces[c][kingPos] = "k";
573 pieces[c][rookPos] = "r";
574 pieces[c][immobilizerPos] = "m";
dac39588 575 }
6808d7a1
BA
576 return (
577 pieces["b"].join("") +
dac39588
BA
578 "/pppppppp/8/8/8/8/PPPPPPPP/" +
579 pieces["w"].join("").toUpperCase() +
6808d7a1
BA
580 " w 0"
581 );
dac39588
BA
582 }
583
6808d7a1 584 getNotation(move) {
dac39588
BA
585 const initialSquare = V.CoordsToSquare(move.start);
586 const finalSquare = V.CoordsToSquare(move.end);
587 let notation = undefined;
6808d7a1 588 if (move.appear[0].p == V.PAWN) {
dac39588
BA
589 // Pawn: generally ambiguous short notation, so we use full description
590 notation = "P" + initialSquare + finalSquare;
6808d7a1
BA
591 } else if (move.appear[0].p == V.KING)
592 notation = "K" + (move.vanish.length > 1 ? "x" : "") + finalSquare;
593 else notation = move.appear[0].p.toUpperCase() + finalSquare;
e9b736ee
BA
594 // Add a capture mark (not describing what is captured...):
595 if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X";
dac39588
BA
596 return notation;
597 }
6808d7a1 598};