Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Musketeer.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class MusketeerRules extends ChessRules {
5
6 // Extra pieces get strange letters because many taken by combinations below
7 static get LEOPARD() {
8 return "d";
9 }
10 static get CANNON() {
11 return "w";
12 }
13 static get UNICORN() {
14 return "x";
15 }
16 static get ELEPHANT() {
17 return "e";
18 }
19 static get HAWK() {
20 return "h";
21 }
22 static get FORTRESS() {
23 return "f";
24 }
25 static get SPIDER() {
26 return "y";
27 }
28
29 static get RESERVE_PIECES() {
30 return (
31 [V.LEOPARD, V.CANNON, V.UNICORN, V.ELEPHANT,
32 V.HAWK, V.FORTRESS, V.SPIDER]
33 );
34 }
35
36 static get PIECES() {
37 return ChessRules.PIECES.concat(V.RESERVE_PIECES);
38 }
39
40 // Decode if normal piece, or + piece1 or piece2
41 getPiece(i, j) {
42 if (i >= V.size.x) return V.RESERVE_PIECES[j];
43 const piece = this.board[i][j].charAt(1);
44 if (V.PIECES.includes(piece)) return piece;
45 // Augmented piece:
46 switch (piece) {
47 case 'a':
48 case 'c':
49 return 'b';
50 case 'j':
51 case 'l':
52 return 'k';
53 case 'm':
54 case 'o':
55 return 'n';
56 case 's':
57 case 't':
58 return 'q';
59 case 'u':
60 case 'v':
61 return 'r';
62 }
63 }
64
65 getColor(i, j) {
66 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
67 return this.board[i][j].charAt(0);
68 }
69
70 // Code: a/c = bishop + piece1/piece2 j/l for king,
71 // m/o for knight, s/t for queen, u/v for rook
72 static get AUGMENTED_PIECES() {
73 return [
74 'a',
75 'c',
76 'j',
77 'l',
78 'm',
79 'o',
80 's',
81 't',
82 'u',
83 'v'
84 ];
85 }
86
87 getPpath(b) {
88 return (ChessRules.PIECES.includes(b[1]) ? "" : "Musketeer/") + b;
89 }
90
91 getReservePpath(index, color) {
92 return "Musketeer/" + color + V.RESERVE_PIECES[index];
93 }
94
95 // Decode above notation into additional piece
96 getExtraPiece(symbol) {
97 if (['a','j','m','s','u'].includes(symbol))
98 return this.extraPieces[0];
99 return this.extraPieces[1];
100 }
101
102 // Inverse operation: augment piece
103 getAugmented(piece) {
104 const p1 = [2, 3].includes(this.movesCount);
105 switch (piece) {
106 case V.ROOK: return (p1 ? 'u' : 'v');
107 case V.KNIGHT: return (p1 ? 'm' : 'o');
108 case V.BISHOP: return (p1 ? 'a' : 'c');
109 case V.QUEEN: return (p1 ? 's' : 't');
110 case V.KING: return (p1 ? 'j' : 'l');
111 }
112 return '_'; //never reached
113 }
114
115 static IsGoodFen(fen) {
116 if (!ChessRules.IsGoodFen(fen)) return false;
117 const fenParsed = V.ParseFen(fen);
118 // 5) Check extra pieces
119 if (!fenParsed.extraPieces) return false;
120 // Not exact matching (would need to look at movesCount), but OK for now
121 if (!fenParsed.extraPieces.match(/^[dwxejfy-]{2,2}$/)) return false;
122 return true;
123 }
124
125 static IsGoodPosition(position) {
126 if (position.length == 0) return false;
127 const rows = position.split("/");
128 if (rows.length != V.size.x) return false;
129 let kings = { "w": 0, "b": 0 };
130 const allPiecesCodes = V.PIECES.concat(V.AUGMENTED_PIECES);
131 const kingBlackCodes = ['j','k','l'];
132 const kingWhiteCodes = ['J','K','L'];
133 for (let row of rows) {
134 let sumElts = 0;
135 for (let i = 0; i < row.length; i++) {
136 if (kingBlackCodes.includes(row[i])) kings['b']++;
137 else if (kingWhiteCodes.includes(row[i])) kings['w']++;
138 if (allPiecesCodes.includes(row[i].toLowerCase())) sumElts++;
139 else {
140 const num = parseInt(row[i], 10);
141 if (isNaN(num)) return false;
142 sumElts += num;
143 }
144 }
145 if (sumElts != V.size.y) return false;
146 }
147 // Both kings should be on board, only one of each color:
148 if (Object.values(kings).some(v => v != 1)) return false;
149 return true;
150 }
151
152 static ParseFen(fen) {
153 const fenParts = fen.split(" ");
154 return Object.assign(
155 ChessRules.ParseFen(fen),
156 { extraPieces: fenParts[5] }
157 );
158 }
159
160 static GenRandInitFen(randomness) {
161 return ChessRules.GenRandInitFen(randomness) + " --";
162 }
163
164 getFen() {
165 return super.getFen() + " " + this.extraPieces.join("");
166 }
167
168 setOtherVariables(fen) {
169 super.setOtherVariables(fen);
170 // Extra pieces may not be defined yet (thus '-')
171 this.extraPieces = V.ParseFen(fen).extraPieces.split("");
172 // At early stages, also init reserves
173 if (this.movesCount <= 5) {
174 const condShow = (piece) => {
175 if (this.movesCount == 0) return true;
176 if (this.movesCount == 1) return piece != this.extraPieces[0];
177 if (this.movesCount <= 3) return this.extraPiece.includes(piece);
178 return this.extraPiece[1] == piece;
179 }
180 this.reserve = { w : {}, b: {} };
181 for (let c of ['w', 'b']) {
182 V.RESERVE_PIECES.forEach(p =>
183 this.reserve[c][p] = condShow(p) ? 1 : 0);
184 }
185 }
186 }
187
188 // Kings may be augmented:
189 scanKings(fen) {
190 this.kingPos = { w: [-1, -1], b: [-1, -1] };
191 const rows = V.ParseFen(fen).position.split("/");
192 for (let i = 0; i < rows.length; i++) {
193 let k = 0; //column index on board
194 for (let j = 0; j < rows[i].length; j++) {
195 const piece = rows[i].charAt(j);
196 if (['j','k','l'].includes(piece.toLowerCase())) {
197 const color = (piece.charCodeAt(0) <= 90 ? 'w' : 'b');
198 this.kingPos[color] = [i, k];
199 }
200 else {
201 const num = parseInt(rows[i].charAt(j), 10);
202 if (!isNaN(num)) k += num - 1;
203 }
204 k++;
205 }
206 }
207 }
208
209 getReserveMoves([x, y]) {
210 const color = this.turn;
211 const p = V.RESERVE_PIECES[y];
212 if (
213 this.reserve[color][p] == 0 ||
214 ([2, 3].includes(this.movesCount) && p != this.extraPieces[0]) ||
215 ([4, 5].includes(this.movesCount) && p != this.extraPieces[1])
216 ) {
217 return [];
218 }
219 let moves = [];
220 const iIdx =
221 (this.movesCount <= 1 ? [2, 3, 4, 5] : [color == 'w' ? 7 : 0]);
222 const mappingAppear = [ [3, 4], [3, 3], [4, 4], [4, 3] ];
223 for (let i of iIdx) {
224 for (let j = 0; j < V.size.y; j++) {
225 if (
226 (this.movesCount <= 1 && this.board[i][j] == V.EMPTY) ||
227 (
228 this.movesCount >= 2 &&
229 ChessRules.PIECES.includes(this.board[i][j].charAt(1))
230 )
231 ) {
232 const [appearX, appearY] =
233 this.movesCount <= 1
234 ? mappingAppear[this.movesCount]
235 : [i, j];
236 const pOnBoard =
237 (this.movesCount >= 2 ? this.board[i][j].charAt(1) : '');
238 let mv = new Move({
239 appear: [
240 new PiPo({
241 x: appearX,
242 y: appearY,
243 c: color,
244 p: (this.movesCount <= 1 ? p : this.getAugmented(pOnBoard))
245 })
246 ],
247 vanish: [],
248 start: { x: x, y: y }, //a bit artificial...
249 end: { x: i, y: j }
250 });
251 if (this.movesCount >= 2)
252 mv.vanish.push(new PiPo({ x: i, y: j, c: color, p: pOnBoard }))
253 moves.push(mv);
254 }
255 }
256 }
257 return moves;
258 }
259
260 // Assumption: movesCount >= 6
261 getPotentialMovesFrom([x, y]) {
262 // Standard moves. If piece not in usual list, new piece appears.
263 const initialPiece = this.getPiece(x, y);
264 if (V.RESERVE_PIECES.includes(initialPiece)) {
265 switch (initialPiece) {
266 case V.LEOPARD: return this.getPotentialLeopardMoves([x, y]);
267 case V.CANNON: return this.getPotentialCannonMoves([x, y]);
268 case V.UNICORN: return this.getPotentialUnicornMoves([x, y]);
269 case V.ELEPHANT: return this.getPotentialElephantMoves([x, y]);
270 case V.HAWK: return this.getPotentialHawkMoves([x, y]);
271 case V.FORTRESS: return this.getPotentialFortressMoves([x, y]);
272 case V.SPIDER: return this.getPotentialSpiderMoves([x, y]);
273 }
274 return []; //never reached
275 }
276 // Following is mostly copy-paste from Titan Chess (TODO?)
277 let moves = [];
278 if (initialPiece == V.PAWN) {
279 const promotions =
280 ChessRules.PawnSpecs.promotions.concat(this.extraPieces);
281 moves = super.getPotentialPawnMoves([x, y], promotions);
282 }
283 else moves = super.getPotentialMovesFrom([x, y]);
284 const color = this.turn;
285 if (
286 ((color == 'w' && x == 7) || (color == "b" && x == 0)) &&
287 V.AUGMENTED_PIECES.includes(this.board[x][y][1])
288 ) {
289 const newPiece = this.getExtraPiece(this.board[x][y][1]);
290 moves.forEach(m => {
291 m.appear[0].p = initialPiece;
292 m.appear.push(
293 new PiPo({
294 p: newPiece,
295 c: color,
296 x: x,
297 y: y
298 })
299 );
300 });
301 moves.forEach(m => {
302 if (m.vanish.length <= 1) return;
303 const [vx, vy] = [m.vanish[1].x, m.vanish[1].y];
304 if (
305 m.appear.length >= 2 && //3 if the king was also augmented
306 m.vanish.length == 2 &&
307 m.vanish[1].c == color &&
308 V.AUGMENTED_PIECES.includes(this.board[vx][vy][1])
309 ) {
310 // Castle, rook is an "augmented piece"
311 m.appear[1].p = V.ROOK;
312 m.appear.push(
313 new PiPo({
314 p: this.getExtraPiece(this.board[vx][vy][1]),
315 c: color,
316 x: vx,
317 y: vy
318 })
319 );
320 }
321 });
322 }
323 return moves;
324 }
325
326 // All types of leaps used here:
327 static get Leap2Ortho() {
328 return [ [-2, 0], [0, -2], [2, 0], [0, 2] ];
329 }
330 static get Leap2Diago() {
331 return [ [-2, -2], [-2, 2], [2, -2], [2, 2] ];
332 }
333 static get Leap3Ortho() {
334 return [ [-3, 0], [0, -3], [3, 0], [0, 3] ];
335 }
336 static get Leap3Diago() {
337 return [ [-3, -3], [-3, 3], [3, -3], [3, 3] ];
338 }
339 static get CamelSteps() {
340 return [
341 [-3, -1], [-3, 1], [-1, -3], [-1, 3],
342 [1, -3], [1, 3], [3, -1], [3, 1]
343 ];
344 }
345 static get VerticalKnight() {
346 return [ [-2, -1], [-2, 1], [2, -1], [2, 1] ];
347 }
348 static get HorizontalKnight() {
349 return [ [-1, -2], [-1, 2], [1, -2], [1, 2] ];
350 }
351
352 getPotentialLeopardMoves(sq) {
353 return (
354 this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 2)
355 .concat(super.getPotentialKnightMoves(sq))
356 );
357 }
358
359 getPotentialCannonMoves(sq) {
360 const steps =
361 V.steps[V.ROOK].concat(V.steps[V.BISHOP])
362 .concat(V.Leap2Ortho).concat(V.HorizontalKnight);
363 return super.getSlideNJumpMoves(sq, steps, 1);
364 }
365
366 getPotentialUnicornMoves(sq) {
367 return (
368 super.getPotentialKnightMoves(sq)
369 .concat(super.getSlideNJumpMoves(sq, V.CamelSteps, 1))
370 );
371 }
372
373 getPotentialElephantMoves(sq) {
374 const steps =
375 V.steps[V.ROOK].concat(V.steps[V.BISHOP])
376 .concat(V.Leap2Ortho)
377 .concat(V.Leap2Diago);
378 return super.getSlideNJumpMoves(sq, steps, 1);
379 }
380
381 getPotentialHawkMoves(sq) {
382 const steps =
383 V.Leap2Ortho.concat(V.Leap2Diago)
384 .concat(V.Leap3Ortho).concat(V.Leap3Diago);
385 return super.getSlideNJumpMoves(sq, steps, 1);
386 }
387
388 getPotentialFortressMoves(sq) {
389 const steps = V.Leap2Ortho.concat(V.VerticalKnight)
390 return (
391 super.getSlideNJumpMoves(sq, steps, 1)
392 .concat(this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 3))
393 );
394 }
395
396 getPotentialSpiderMoves(sq) {
397 const steps = V.Leap2Ortho.concat(V.steps[V.KNIGHT])
398 return (
399 super.getSlideNJumpMoves(sq, steps, 1)
400 .concat(this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 2))
401 );
402 }
403
404 getPossibleMovesFrom([x, y]) {
405 if (this.movesCount <= 5)
406 return (x >= V.size.x ? this.getReserveMoves([x, y]) : []);
407 return super.getPossibleMovesFrom([x, y]);
408 }
409
410 getAllValidMoves() {
411 if (this.movesCount >= 6) return super.getAllValidMoves();
412 let moves = [];
413 const color = this.turn;
414 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
415 moves = moves.concat(
416 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
417 );
418 }
419 return moves;
420 }
421
422 atLeastOneMove() {
423 if (this.movesCount <= 5) return true;
424 return super.atLeastOneMove();
425 }
426
427 isAttacked(sq, color) {
428 if (super.isAttacked(sq, color)) return true;
429 if (
430 this.extraPieces.includes(V.LEOPARD) &&
431 this.isAttackedByLeopard(sq, color)
432 ) {
433 return true;
434 }
435 if (
436 this.extraPieces.includes(V.CANNON) &&
437 this.isAttackedByCannon(sq, color)
438 ) {
439 return true;
440 }
441 if (
442 this.extraPieces.includes(V.UNICORN) &&
443 this.isAttackedByUnicorn(sq, color)
444 ) {
445 return true;
446 }
447 if (
448 this.extraPieces.includes(V.ELEPHANT) &&
449 this.isAttackedByElephant(sq, color)
450 ) {
451 return true;
452 }
453 if (
454 this.extraPieces.includes(V.HAWK) &&
455 this.isAttackedByHawk(sq, color)
456 ) {
457 return true;
458 }
459 if (
460 this.extraPieces.includes(V.FORTRESS) &&
461 this.isAttackedByFortress(sq, color)
462 ) {
463 return true;
464 }
465 if (
466 this.extraPieces.includes(V.SPIDER) &&
467 this.isAttackedBySpider(sq, color)
468 ) {
469 return true;
470 }
471 return false;
472 }
473
474 isAttackedByLeopard(sq, color) {
475 return (
476 super.isAttackedBySlideNJump(
477 sq, color, V.LEOPARD, V.steps[V.KNIGHT], 1) ||
478 this.isAttackedBySlideNJump(sq, color, V.LEOPARD, V.steps[V.BISHOP], 2)
479 );
480 }
481
482 isAttackedByCannon(sq, color) {
483 const steps =
484 V.steps[V.ROOK].concat(V.steps[V.BISHOP])
485 .concat(V.Leap2Ortho).concat(V.HorizontalKnight);
486 return super.isAttackedBySlideNJump(sq, color, V.CANNON, steps, 1);
487 }
488
489 isAttackedByUnicorn(sq, color) {
490 const steps = V.steps[V.KNIGHT].concat(V.CamelSteps)
491 return (
492 super.isAttackedBySlideNJump(sq, color, V.UNICORN, steps, 1)
493 );
494 }
495
496 isAttackedByElephant(sq, color) {
497 const steps =
498 V.steps[V.ROOK].concat(V.steps[V.BISHOP])
499 .concat(V.Leap2Ortho)
500 .concat(V.Leap2Diago);
501 return (
502 super.isAttackedBySlideNJump(sq, color, V.ELEPHANT, steps, 1)
503 );
504 }
505
506 isAttackedByHawk(sq, color) {
507 const steps =
508 V.Leap2Ortho.concat(V.Leap2Diago)
509 .concat(V.Leap3Ortho).concat(V.Leap3Diago);
510 return super.isAttackedBySlideNJump(sq, color, V.HAWK, steps, 1);
511 }
512
513 isAttackedByFortress(sq, color) {
514 const steps = V.Leap2Ortho.concat(V.VerticalKnight)
515 return (
516 super.isAttackedBySlideNJump(sq, color, V.FORTRESS, steps, 1) ||
517 this.isAttackedBySlideNJump(sq, color, V.FORTRESS, V.steps[V.BISHOP], 3)
518 );
519 }
520
521 isAttackedBySpider(sq, color) {
522 const steps = V.Leap2Ortho.concat(V.steps[V.KNIGHT])
523 return (
524 super.isAttackedBySlideNJump(sq, color, V.SPIDER, steps, 1) ||
525 this.isAttackedBySlideNJump(sq, color, V.SPIDER, V.steps[V.BISHOP], 2)
526 );
527 }
528
529 getCheckSquares() {
530 if (this.movesCount <= 6) return [];
531 return super.getCheckSquares();
532 }
533
534 // At movesCount == 0,1: show full reserves [minus chosen piece1]
535 // At movesCount == 2,3: show reserve with only 2 selected pieces
536 // At movesCount == 4,5: show reserve with only piece2
537 // Then, no reserve.
538 postPlay(move) {
539 if (this.movesCount > 6) super.postPlay(move);
540 else {
541 switch (this.movesCount) {
542 case 1:
543 this.reserve['w'][move.appear[0].p]--;
544 this.reserve['b'][move.appear[0].p]--;
545 this.extraPieces[0] = move.appear[0].p;
546 break;
547 case 2:
548 this.extraPieces[1] = move.appear[0].p;
549 for (let p of V.RESERVE_PIECES) {
550 const resVal = (this.extraPieces.includes(p) ? 1 : 0);
551 this.reserve['w'][p] = resVal;
552 this.reserve['b'][p] = resVal;
553 }
554 break;
555 case 3:
556 this.reserve['w'][this.extraPieces[0]]--;
557 break;
558 case 4:
559 this.reserve['b'][this.extraPieces[0]]--;
560 break;
561 case 5:
562 this.reserve['w'][this.extraPieces[1]]--;
563 break;
564 case 6:
565 this.reserve = null;
566 this.board[3][3] = "";
567 this.board[3][4] = "";
568 break;
569 }
570 }
571 }
572
573 postUndo(move) {
574 if (this.movesCount >= 6) super.postUndo(move);
575 else {
576 switch (this.movesCount) {
577 case 0:
578 this.reserve['w'][move.appear[0].p]++;
579 this.reserve['b'][move.appear[0].p]++;
580 this.extraPieces[0] = '-';
581 break;
582 case 1:
583 this.extraPieces[1] = '-';
584 for (let p of V.RESERVE_PIECES) {
585 const resVal = (p != this.extraPieces[0] ? 1 : 0);
586 this.reserve['w'][p] = resVal;
587 this.reserve['b'][p] = resVal;
588 }
589 break;
590 case 2:
591 this.reserve['w'][this.extraPieces[0]]++;
592 break;
593 case 3:
594 this.reserve['b'][this.extraPieces[0]]++;
595 break;
596 case 4:
597 this.reserve['w'][this.extraPieces[1]]++;
598 break;
599 case 5:
600 this.reserve = { w: {}, b: {} };
601 for (let c of ['w', 'b'])
602 V.RESERVE_PIECES.forEach(p => this.reserve[c][p] = 0);
603 this.reserve['b'][this.extraPieces[1]] = 1;
604 this.board[3][3] = 'b' + this.extraPieces[1];
605 this.board[3][4] = 'w' + this.extraPieces[0];
606 break;
607 }
608 }
609 }
610
611 getComputerMove() {
612 if (this.movesCount >= 6) return super.getComputerMove();
613 // Choose a move at random
614 const moves = this.getAllValidMoves();
615 return moves[randInt(moves.length)];
616 }
617
618 static get SEARCH_DEPTH() {
619 return 2;
620 }
621
622 evalPosition() {
623 let evaluation = 0;
624 for (let i = 0; i < V.size.x; i++) {
625 for (let j = 0; j < V.size.y; j++) {
626 if (this.board[i][j] != V.EMPTY) {
627 const sign = this.getColor(i, j) == "w" ? 1 : -1;
628 const piece = this.getPiece(i, j);
629 evaluation += sign * V.VALUES[piece];
630 const symbol = this.board[i][j][1];
631 if (V.AUGMENTED_PIECES.includes(symbol)) {
632 const extraPiece = this.getExtraPiece(symbol);
633 evaluation += sign * V.VALUES[extraPiece]
634 }
635 }
636 }
637 }
638 return evaluation;
639 }
640
641 static get VALUES() {
642 return Object.assign(
643 {
644 d: 6.7,
645 w: 7.5,
646 x: 5.6,
647 e: 6.3,
648 h: 5.5,
649 f: 7.6,
650 y: 8.15
651 },
652 ChessRules.VALUES
653 );
654 }
655
656 static get ExtraDictionary() {
657 return {
658 [V.LEOPARD]: { prefix: 'L', name: "Leopard" },
659 [V.CANNON]: { prefix: 'C', name: "Cannon" },
660 [V.UNICORN]: { prefix: 'U', name: "Unicorn" },
661 [V.ELEPHANT]: { prefix: 'E', name: "Elephant" },
662 [V.HAWK]: { prefix: 'H', name: "Hawk" },
663 [V.FORTRESS]: { prefix: 'F', name: "Fortress" },
664 [V.SPIDER]: { prefix: 'S', name: "Spider" }
665 }
666 }
667
668 getNotation(move) {
669 if (this.movesCount <= 5) {
670 if (this.movesCount <= 1)
671 return V.ExtraDictionary[move.appear[0].p].name;
672 // Put something on the board:
673 return (
674 V.ExtraDictionary[V.RESERVE_PIECES[move.start.y]].prefix +
675 "@" + V.CoordsToSquare(move.end)
676 );
677 }
678 let notation = "";
679 if (
680 V.AUGMENTED_PIECES.includes(move.vanish[0].p) ||
681 (
682 move.vanish.length >= 2 &&
683 V.AUGMENTED_PIECES.includes(move.vanish[1].p)
684 )
685 ) {
686 // Simplify move before calling super.getNotation()
687 let smove = JSON.parse(JSON.stringify(move));
688 if (ChessRules.PIECES.includes(move.vanish[0].p)) {
689 // Castle with an augmented rook
690 smove.appear.pop();
691 smove.vanish[1].p = smove.appear[1].p;
692 }
693 else {
694 // Moving an augmented piece
695 smove.appear.pop();
696 smove.vanish[0].p = smove.appear[0].p;
697 if (
698 smove.vanish.length == 2 &&
699 smove.vanish[0].c == smove.vanish[1].c &&
700 V.AUGMENTED_PIECES.includes(move.vanish[1].p)
701 ) {
702 // Castle with an augmented rook
703 smove.appear.pop();
704 smove.vanish[1].p = smove.appear[1].p;
705 }
706 }
707 notation = super.getNotation(smove);
708 }
709 // Else, more common case:
710 notation = super.getNotation(move);
711 const pieceSymbol = notation.charAt(0).toLowerCase();
712 if (move.vanish[0].p != V.PAWN && V.RESERVE_PIECES.includes(pieceSymbol))
713 notation = V.ExtraDictionary[pieceSymbol].prefix + notation.substr(1);
714 return notation;
715 }
716
717 };