schachnovelle 1.0.0-beta-6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ui/Game.js CHANGED
@@ -1,18 +1,17 @@
1
- const readline = require('readline')
2
- const Side = require('./Side')
3
- const Board = require('./Board')
4
- const Printer = require('./Printer')
5
- const Utils = require('./Utils')
6
- const controller = require('../tests/controller')
1
+ const readline = require("readline")
2
+ const Side = require("./Side")
3
+ const Board = require("./Board")
4
+ const Printer = require("./Printer")
5
+ const Utils = require("./Utils")
7
6
 
8
- const { prompt } = require('enquirer');
7
+ const Enquirer = require("enquirer")
9
8
 
10
9
  class Game {
11
- constructor (options) {
12
- this.white = new Side('white')
13
- this.black = new Side('black')
10
+ constructor(options) {
11
+ this.white = new Side("white")
12
+ this.black = new Side("black")
14
13
  this.playingAs = options.playingAs
15
- this.toMove = 'white'
14
+ this.toMove = "white"
16
15
 
17
16
  this.moveIndex = 0
18
17
  this.history = []
@@ -21,12 +20,13 @@ class Game {
21
20
 
22
21
  // Initialize the board
23
22
  this.board = new Board(this.white, this.black, {
24
- mode: 'pieces', // possibly add text mode
23
+ mode: "pieces", // possibly add text mode
25
24
  orientation: this.playingAs,
26
25
  spacing: options.spacing,
27
- mode: options.mode
26
+ mode: options.mode,
28
27
  })
29
28
 
29
+ this.enquirer = new Enquirer()
30
30
  this.printer = new Printer(options.spacing)
31
31
  this.utils = new Utils()
32
32
  }
@@ -36,21 +36,21 @@ class Game {
36
36
  * 2. Sets that as the selected piece
37
37
  */
38
38
  async promptPieceSelection() {
39
- this.printer.print(`${this.toMove} to move.`)
40
- this.printer.addSpace('y')
41
- const response = await prompt([
42
- {
43
- type: 'select',
44
- name: 'piece',
45
- message: 'Piece',
46
- choices: this.listAvailablePieces(),
47
- result: (result) => {
48
- this.setSelectedPiece(result)
49
- return result
50
- }
51
- }
52
- ]).catch(console.error);
53
-
39
+ const response = await this.enquirer
40
+ .prompt([
41
+ {
42
+ type: "select",
43
+ name: "piece",
44
+ message: "Piece",
45
+ choices: this.listAvailablePieces(),
46
+ result: result => {
47
+ this.setSelectedPiece(result)
48
+ return result
49
+ },
50
+ },
51
+ ])
52
+ .catch(console.error)
53
+
54
54
  /**
55
55
  * Only do something if the await has been fulfilled
56
56
  */
@@ -64,24 +64,35 @@ class Game {
64
64
  * 3. Asks for the new coordinates of that piece
65
65
  */
66
66
  async promptFileSelection() {
67
- const response = await prompt([
68
- {
69
- type: 'select',
70
- name: 'file',
71
- message: 'File:',
72
- choices: this.possibleFiles(),
73
- result: (result) => {
74
- this.setSelectedFile(result)
75
- return result
76
- }
77
- }
78
- ]).catch(console.error);
79
-
67
+ const backToPiece = "... piece selection"
68
+ const response = await this.enquirer
69
+ .prompt([
70
+ {
71
+ type: "select",
72
+ name: "file",
73
+ message: "File:",
74
+ choices: this.possibleFiles().concat(backToPiece),
75
+ result: result => {
76
+ if (result === backToPiece) {
77
+ return result
78
+ } else {
79
+ this.setSelectedFile(result)
80
+ return result
81
+ }
82
+ },
83
+ },
84
+ ])
85
+ .catch(console.error)
86
+
80
87
  /**
81
88
  * Only do something if the await has been fulfilled
82
89
  */
83
90
  if (response) {
84
- this.promptRankSelection()
91
+ if (response.file === backToPiece) {
92
+ this.promptPieceSelection()
93
+ } else {
94
+ this.promptRankSelection()
95
+ }
85
96
  }
86
97
  }
87
98
 
@@ -90,126 +101,494 @@ class Game {
90
101
  * 3. Asks for the new coordinates of that piece
91
102
  */
92
103
  async promptRankSelection() {
93
- const response = await prompt([
94
- {
95
- type: 'select',
96
- name: 'rank',
97
- message: 'Rank:',
98
- choices: this.possibleRanks(),
99
- result: (result) => {
100
- this.setSelectedRank(Number(result))
101
- return Number(result)
102
- }
103
- }
104
- ]).catch(console.error);
105
-
104
+ const backToFile = "... file selection"
105
+ const response = await this.enquirer
106
+ .prompt([
107
+ {
108
+ type: "select",
109
+ name: "rank",
110
+ message: "Rank:",
111
+ choices: this.possibleRanks().concat(backToFile),
112
+ result: result => {
113
+ if (result === backToFile) {
114
+ return result
115
+ } else {
116
+ this.setSelectedRank(Number(result))
117
+ return Number(result)
118
+ }
119
+ },
120
+ },
121
+ ])
122
+ .catch(console.error)
123
+
106
124
  /**
107
125
  * Only do something if the await has been fulfilled
108
126
  */
109
127
  if (response) {
110
- this.validateMove()
128
+ if (response.rank === backToFile) {
129
+ this.promptFileSelection()
130
+ } else {
131
+ this.validateMove()
132
+ }
111
133
  }
112
134
  }
113
135
 
114
- allMoves () {
115
- let moves = []
116
- for (const file of this.board.files) {
117
- for (const rank of this.board.ranks) {
118
- if (this.movePotential([file, rank], false) !== 'blocked') { moves.push([file, rank]) }
119
- }
120
- }
121
- return moves
136
+ /**
137
+ * For the given color, return all possible moves
138
+ */
139
+ allPossibleMoves(color) {
140
+ let all = []
141
+ this.board[color].pieces
142
+ .filter(p => !p.captured)
143
+ .forEach(p => {
144
+ this.possibleMoves(p, color)
145
+ .forEach(m => all.push(m))
146
+ })
147
+ return all
148
+ }
149
+
150
+ /**
151
+ * Return all possible checks
152
+ * @param {*} color
153
+ * @returns
154
+ */
155
+ allPossibleChecks(color) {
156
+ // Moves
157
+ const moves = this.allPossibleMoves(color)
158
+
159
+ const allChecks = moves.filter(m => m[2] === 'check')
160
+
161
+ return allChecks
122
162
  }
123
163
 
124
164
  /**
125
165
  * Return the possible moves for the selected piece
126
166
  * Returns an object with all possible moves split into file, and rank
127
167
  */
128
- possibleMoves (piece) {
168
+ possibleMoves(piece, color) {
169
+ // TODO: If the current color is in check,
170
+ // then limit the possible moves to the ones that get you out of it
171
+
129
172
  // Start with the selected piece. First of all, it can't move to it's own square
130
- // console.log(this.getSelectedPiece())
131
173
  switch (piece.type) {
132
- case ('Pawn'):
133
- return this.possiblePawnMoves(piece)
134
- case ('Knight'):
135
- return this.allMoves()
136
- case ('Bishop'):
137
- return this.allMoves()
138
- case ('Rook'):
139
- return this.allMoves()
140
- case ('Queen'):
141
- return this.allMoves()
142
- case ('King'):
143
- return this.allMoves()
174
+ case "Pawn":
175
+ return this.possiblePawnMoves(piece, color)
176
+ case "Knight":
177
+ return this.possibleKnightMoves(piece, color)
178
+ case "Bishop":
179
+ return this.possibleBishopMoves(piece, color)
180
+ case "Rook":
181
+ return this.possibleRookMoves(piece, color)
182
+ case "Queen":
183
+ return this.possibleQueenMoves(piece, color)
184
+ case "King":
185
+ return this.possibleKingMoves(piece, color)
144
186
  }
145
187
  }
146
188
 
147
189
  /**
148
190
  * Returns all possible moves for a pawn
149
191
  */
150
- possiblePawnMoves (pawn) {
192
+ possiblePawnMoves (pawn, color) {
151
193
  const possible = []
152
194
  const positions = []
153
- const nextRank = this.toMove === 'white' ? pawn.pos[1] + 1 : pawn.pos[1] - 1
154
- const secondNextRank = this.toMove === 'white' ? pawn.pos[1] + 2 : pawn.pos[1] - 2
195
+ const direction = this.playingAs === color ? 1 : -1
196
+
197
+ const nextRank = pawn.pos[1] + direction
198
+ const secondNextRank = pawn.pos[1] + (direction * 2)
155
199
  // If it's on the starting rank, allow two moves
156
- if (pawn.pos[1] === 2 && this.toMove === 'white') {
157
- positions.push(
158
- [pawn.pos[0], nextRank],
159
- [pawn.pos[0], secondNextRank]
160
- )
161
- } else if (pawn.pos[1] === 7 && this.toMove === 'black') {
162
- positions.push(
163
- [pawn.pos[0], nextRank],
164
- [pawn.pos[0], secondNextRank]
165
- )
166
- } else if (this.toMove === 'white') {
167
- positions.push(
168
- [pawn.pos[0], nextRank]
169
- )
200
+ if (pawn.pos[1] === 2 && color === "white") {
201
+ positions.push([pawn.pos[0], nextRank], [pawn.pos[0], secondNextRank])
202
+ } else if (pawn.pos[1] === 7 && color === "black") {
203
+ positions.push([pawn.pos[0], nextRank], [pawn.pos[0], secondNextRank])
204
+ } else if (color === "white") {
205
+ positions.push([pawn.pos[0], nextRank])
170
206
  } else {
171
- positions.push(
172
- [pawn.pos[0], nextRank]
173
- )
207
+ positions.push([pawn.pos[0], nextRank])
174
208
  }
175
209
  // Captures
176
210
  const fileIndex = this.board.files.indexOf(pawn.pos[0])
177
211
  const positionsToCapture = []
178
212
  switch (fileIndex) {
179
- case (0):
180
- const posXL = [ this.board.files[fileIndex + 1], nextRank]
213
+ case 0:
214
+ const posXL = [this.board.files[fileIndex + 1], nextRank]
181
215
  positionsToCapture.push(posXL)
182
216
  break
183
- case (7):
184
- const posXR = [ this.board.files[fileIndex - 1], nextRank ]
217
+ case 7:
218
+ const posXR = [this.board.files[fileIndex - 1], nextRank]
185
219
  positionsToCapture.push(posXR)
186
220
  break
187
221
  default:
188
- const posL = [ this.board.files[fileIndex + 1], nextRank ]
189
- const posR = [ this.board.files[fileIndex - 1], nextRank ]
222
+ const posL = [this.board.files[fileIndex + 1], nextRank]
223
+ const posR = [this.board.files[fileIndex - 1], nextRank]
190
224
  positionsToCapture.push(posL, posR)
191
225
  break
192
226
  }
193
227
  for (const pos of positions) {
194
- if (this.movePotential(pos, false) !== 'blocked') {
228
+ if (this.movePotential(pos, color, false) !== "blocked") {
195
229
  possible.push(pos)
196
230
  }
197
231
  }
198
232
  for (const pos of positionsToCapture) {
199
- if (this.movePotential(pos, true) === 'capture') {
233
+ if (this.movePotential(pos, color, true) === "capture") {
234
+ possible.push(pos)
235
+ } else if (this.movePotential(pos, color, true) === "check") {
236
+ pos.push('check')
200
237
  possible.push(pos)
201
238
  }
202
239
  }
203
240
  // TODO: en passant (depends on a history)
204
- // convert to queen / rook / bishop / knight
241
+ return possible
242
+ }
243
+
244
+ /**
245
+ * Just returns the candidate positions a knight could move to
246
+ * @param {Integer} fileIndex
247
+ * @param {Integer} rankIndex
248
+ */
249
+ knightCandidateMoves(fileIndex, rankIndex) {
250
+ return [
251
+ // Two up, one left
252
+ [this.board.files[fileIndex - 1], this.board.ranks[rankIndex + 2]],
253
+ // Two up, one right
254
+ [this.board.files[fileIndex + 1], this.board.ranks[rankIndex + 2]],
255
+ // Two right, one up
256
+ [this.board.files[fileIndex + 2], this.board.ranks[rankIndex + 1]],
257
+ // Two right, one down
258
+ [this.board.files[fileIndex + 2], this.board.ranks[rankIndex - 1]],
259
+ // Two down, one right
260
+ [this.board.files[fileIndex + 1], this.board.ranks[rankIndex - 2]],
261
+ // Two down, one left
262
+ [this.board.files[fileIndex - 1], this.board.ranks[rankIndex - 2]],
263
+ // Two left, one down
264
+ [this.board.files[fileIndex - 2], this.board.ranks[rankIndex - 1]],
265
+ // Two left, one up
266
+ [this.board.files[fileIndex - 2], this.board.ranks[rankIndex + 1]],
267
+ ]
268
+ }
269
+
270
+ /**
271
+ * Returns the possible moves for a knight
272
+ */
273
+ possibleKnightMoves(knight, color) {
274
+ const fileIndex = this.board.files.indexOf(knight.pos[0])
275
+ const rankIndex = this.board.ranks.indexOf(knight.pos[1])
276
+ // From the starting square, draw a plus in each direction and then go one diagonal from there
277
+ // Take the starting file and go left and right
278
+ const candidates = this.knightCandidateMoves(fileIndex, rankIndex)
279
+
280
+ const possible = candidates.filter(move => {
281
+ if (move[0] !== undefined && move[1] !== undefined) {
282
+ if (this.movePotential(move, color, true) === "check") {
283
+ move.push('check')
284
+ return move
285
+ } else if (this.movePotential(move, color, true) !== "blocked") {
286
+ return move
287
+ }
288
+ }
289
+ })
290
+ return possible
291
+ }
292
+
293
+ /**
294
+ * Returns the possible moves for a bishop
295
+ */
296
+ possibleBishopMoves(bishop, color) {
297
+ // go into all diagonal directions until there's no place or there's a capture or there's a block
298
+ const fileIndex = this.board.files.indexOf(bishop.pos[0])
299
+ const rankIndex = this.board.ranks.indexOf(bishop.pos[1])
300
+
301
+ const fU = this.forwardAndUp(fileIndex, rankIndex, color)
302
+ const fD = this.forwardAndDown(fileIndex, rankIndex, color, false)
303
+ const bU = this.backwardAndUp(fileIndex, rankIndex, color, false)
304
+ const bD = this.backwardAndDown(fileIndex, rankIndex, color, false)
305
+
306
+ return [...fU, ...fD, ...bU, ...bD]
307
+ }
308
+
309
+ /**
310
+ * Returns the possible moves for a rook
311
+ */
312
+ possibleRookMoves(rook, color) {
313
+ // go into all diagonal directions until there's no place or there's a capture or there's a block
314
+ const fileIndex = this.board.files.indexOf(rook.pos[0])
315
+ const rankIndex = this.board.ranks.indexOf(rook.pos[1])
316
+
317
+ const u = this.up(fileIndex, rankIndex, color, false)
318
+ const d = this.down(fileIndex, rankIndex, color, false)
319
+ const l = this.left(fileIndex, rankIndex, color, false)
320
+ const r = this.right(fileIndex, rankIndex, color, false)
321
+
322
+ return [...u, ...d, ...l, ...r]
323
+ }
324
+
325
+ /**
326
+ * Returns the possible moves for a queen
327
+ */
328
+ possibleQueenMoves(queen, color) {
329
+ // go into all diagonal directions until there's no place or there's a capture or there's a block
330
+ const fileIndex = this.board.files.indexOf(queen.pos[0])
331
+ const rankIndex = this.board.ranks.indexOf(queen.pos[1])
332
+
333
+ const u = this.up(fileIndex, rankIndex, color, false)
334
+ const d = this.down(fileIndex, rankIndex, color, false)
335
+ const l = this.left(fileIndex, rankIndex, color, false)
336
+ const r = this.right(fileIndex, rankIndex, color, false)
337
+ const fU = this.forwardAndUp(fileIndex, rankIndex, color, false)
338
+ const fD = this.forwardAndDown(fileIndex, rankIndex, color, false)
339
+ const bU = this.backwardAndUp(fileIndex, rankIndex, color, false)
340
+ const bD = this.backwardAndDown(fileIndex, rankIndex, color, false)
341
+
342
+ return [...u, ...d, ...l, ...r, ...fU, ...fD, ...bU, ...bD]
343
+ }
344
+
345
+ /**
346
+ * Returns the possible moves for a queen
347
+ */
348
+ possibleKingMoves(king, color) {
349
+ // go into all diagonal directions until there's no place or there's a capture or there's a block
350
+ const fileIndex = this.board.files.indexOf(king.pos[0])
351
+ const rankIndex = this.board.ranks.indexOf(king.pos[1])
352
+
353
+ const u = this.up(fileIndex, rankIndex, color, true)
354
+ const d = this.down(fileIndex, rankIndex, color, true)
355
+ const l = this.left(fileIndex, rankIndex, color, true)
356
+ const r = this.right(fileIndex, rankIndex, color, true)
357
+ const fU = this.forwardAndUp(fileIndex, rankIndex, color, true)
358
+ const fD = this.forwardAndDown(fileIndex, rankIndex, color, true)
359
+ const bU = this.backwardAndUp(fileIndex, rankIndex, color, true)
360
+ const bD = this.backwardAndDown(fileIndex, rankIndex, color, true)
361
+
362
+ return [...u, ...d, ...l, ...r, ...fU, ...fD, ...bU, ...bD]
363
+ }
364
+
365
+ /**
366
+ * Returns the possible moves on a diagonal going right and up
367
+ * This function is used in two contexts;
368
+ * 1. To determine the next moves for a person playing
369
+ * 2. To check if the next move will be checking the opponent's king
370
+ */
371
+ forwardAndUp(fileIndex, rankIndex, color, king) {
372
+ const possible = []
373
+ let fi = fileIndex + 1
374
+ let ri = rankIndex + 1
375
+
376
+ let nextPosition = [this.board.files[fi], this.board.ranks[ri]]
377
+
378
+ while (
379
+ this.movePotential(nextPosition, color, true) !== "blocked" &&
380
+ this.movePotential(nextPosition, color, true) !== "impossible"
381
+ ) {
382
+ if (this.movePotential(nextPosition, color, true) === "check") {
383
+ nextPosition.push('check')
384
+ possible.push(nextPosition)
385
+ break
386
+ }
387
+ possible.push(nextPosition)
388
+ if (this.movePotential(nextPosition, color, true) === "capture" || king) break
389
+ fi++
390
+ ri++
391
+ nextPosition = [this.board.files[fi], this.board.ranks[ri]]
392
+ }
393
+
394
+ return possible
395
+ }
396
+
397
+ /**
398
+ * Returns the possible moves on a diagonal going right and down
399
+ */
400
+ forwardAndDown(fileIndex, rankIndex, color, king) {
401
+ const possible = []
402
+ let fi = fileIndex + 1
403
+ let ri = rankIndex + -1
404
+
405
+ let nextPosition = [this.board.files[fi], this.board.ranks[ri]]
406
+
407
+ while (
408
+ this.movePotential(nextPosition, color, true) !== "blocked" &&
409
+ this.movePotential(nextPosition, color, true) !== "impossible"
410
+ ) {
411
+ if (this.movePotential(nextPosition, color, true) === "check") {
412
+ nextPosition.push('check')
413
+ possible.push(nextPosition)
414
+ break
415
+ }
416
+ possible.push(nextPosition)
417
+ if (this.movePotential(nextPosition, color, true) === "capture" || king) break
418
+ fi++
419
+ ri--
420
+ nextPosition = [this.board.files[fi], this.board.ranks[ri]]
421
+ }
422
+ return possible
423
+ }
424
+
425
+ /**
426
+ * Returns the possible moves on a diagonal going left and up
427
+ */
428
+ backwardAndUp(fileIndex, rankIndex, color, king) {
429
+ const possible = []
430
+ let fi = fileIndex - 1
431
+ let ri = rankIndex + 1
432
+
433
+ let nextPosition = [this.board.files[fi], this.board.ranks[ri]]
434
+
435
+ while (
436
+ this.movePotential(nextPosition, color, true) !== "blocked" &&
437
+ this.movePotential(nextPosition, color, true) !== "impossible"
438
+ ) {
439
+ if (this.movePotential(nextPosition, color, true) === "check") {
440
+ nextPosition.push('check')
441
+ possible.push(nextPosition)
442
+ break
443
+ }
444
+ possible.push(nextPosition)
445
+ if (this.movePotential(nextPosition, color, true) === "capture" || king) break
446
+ fi--
447
+ ri++
448
+ nextPosition = [this.board.files[fi], this.board.ranks[ri]]
449
+ }
450
+ return possible
451
+ }
452
+
453
+ /**
454
+ * Returns the possible moves on a diagonal going left and down
455
+ */
456
+ backwardAndDown(fileIndex, rankIndex, color, king) {
457
+ const possible = []
458
+ let fi = fileIndex - 1
459
+ let ri = rankIndex - 1
460
+
461
+ let nextPosition = [this.board.files[fi], this.board.ranks[ri]]
462
+
463
+ while (
464
+ this.movePotential(nextPosition, color, true) !== "blocked" &&
465
+ this.movePotential(nextPosition, color, true) !== "impossible"
466
+ ) {
467
+ if (this.movePotential(nextPosition, color, true) === "check") {
468
+ nextPosition.push('check')
469
+ possible.push(nextPosition)
470
+ break
471
+ }
472
+ possible.push(nextPosition)
473
+ if (this.movePotential(nextPosition, color, true) === "capture" || king) break
474
+ fi--
475
+ ri--
476
+ nextPosition = [this.board.files[fi], this.board.ranks[ri]]
477
+ }
478
+ return possible
479
+ }
480
+
481
+ /**
482
+ * Returns the possible moves on a file going up
483
+ */
484
+ up(fileIndex, rankIndex, color, king) {
485
+ const possible = []
486
+ let ri = rankIndex + 1
487
+
488
+ let nextPosition = [this.board.files[fileIndex], this.board.ranks[ri]]
489
+
490
+ while (
491
+ this.movePotential(nextPosition, color, true) !== "blocked" &&
492
+ this.movePotential(nextPosition, color, true) !== "impossible"
493
+ ) {
494
+ if (this.movePotential(nextPosition, color, true) === "check") {
495
+ nextPosition.push('check')
496
+ possible.push(nextPosition)
497
+ break
498
+ }
499
+ possible.push(nextPosition)
500
+ if (this.movePotential(nextPosition, color, true) === "capture" || king) break
501
+ ri++
502
+ nextPosition = [this.board.files[fileIndex], this.board.ranks[ri]]
503
+ }
504
+ return possible
505
+ }
506
+
507
+ /**
508
+ * Returns the possible moves on a file going down
509
+ */
510
+ down(fileIndex, rankIndex, color, king) {
511
+ const possible = []
512
+ let ri = rankIndex - 1
513
+
514
+ let nextPosition = [this.board.files[fileIndex], this.board.ranks[ri]]
515
+
516
+ while (
517
+ this.movePotential(nextPosition, color, true) !== "blocked" &&
518
+ this.movePotential(nextPosition, color, true) !== "impossible"
519
+ ) {
520
+ if (this.movePotential(nextPosition, color, true) === "check") {
521
+ nextPosition.push('check')
522
+ possible.push(nextPosition)
523
+ break
524
+ }
525
+ possible.push(nextPosition)
526
+ if (this.movePotential(nextPosition, color, true) === "capture" || king) break
527
+ ri--
528
+ nextPosition = [this.board.files[fileIndex], this.board.ranks[ri]]
529
+ }
530
+ return possible
531
+ }
532
+
533
+ /**
534
+ * Returns the possible moves on a rank going left
535
+ */
536
+ left(fileIndex, rankIndex, color, king) {
537
+ const possible = []
538
+ let fi = fileIndex - 1
539
+
540
+ let nextPosition = [this.board.files[fi], this.board.ranks[rankIndex]]
541
+
542
+ while (
543
+ this.movePotential(nextPosition, color, true) !== "blocked" &&
544
+ this.movePotential(nextPosition, color, true) !== "impossible"
545
+ ) {
546
+ if (this.movePotential(nextPosition, color, true) === "check") {
547
+ nextPosition.push('check')
548
+ possible.push(nextPosition)
549
+ break
550
+ }
551
+ possible.push(nextPosition)
552
+ if (this.movePotential(nextPosition, color, true) === "capture" || king) break
553
+ fi--
554
+ nextPosition = [this.board.files[fi], this.board.ranks[rankIndex]]
555
+ }
556
+ return possible
557
+ }
558
+
559
+ /**
560
+ * Returns the possible moves on a rank going right
561
+ */
562
+ right(fileIndex, rankIndex, color, king) {
563
+ const possible = []
564
+ let fi = fileIndex + 1
565
+
566
+ let nextPosition = [this.board.files[fi], this.board.ranks[rankIndex]]
567
+
568
+ while (
569
+ this.movePotential(nextPosition, color, true) !== "blocked" &&
570
+ this.movePotential(nextPosition, color, true) !== "impossible"
571
+ ) {
572
+ if (this.movePotential(nextPosition, color, true) === "check") {
573
+ nextPosition.push('check')
574
+ possible.push(nextPosition)
575
+ break
576
+ }
577
+ possible.push(nextPosition)
578
+ if (this.movePotential(nextPosition, color, true) === "capture" || king) break
579
+ fi++
580
+ nextPosition = [this.board.files[fi], this.board.ranks[rankIndex]]
581
+ }
205
582
  return possible
206
583
  }
207
584
 
208
585
  /**
209
586
  * Returns the possible ranks from the possiblemoves function
210
587
  */
211
- possibleFiles () {
212
- const possible = this.possibleMoves(this.selectedPiece).map((pos) => { return pos[0] })
588
+ possibleFiles() {
589
+ const possible = this.possibleMoves(this.selectedPiece, this.toMove).map(pos => {
590
+ return pos[0]
591
+ })
213
592
  /** Filter unique */
214
593
  return [...new Set(possible)]
215
594
  }
@@ -217,52 +596,70 @@ class Game {
217
596
  /**
218
597
  * Returns the possible ranks from the possiblemoves function
219
598
  */
220
- possibleRanks () {
221
- const possible = this.possibleMoves(this.selectedPiece).filter((move) => {
222
- if (move[0] === this.selectedFile) { return move[1] }
223
- }).map((pos) => {
224
- return pos[1].toString()
225
- })
599
+ possibleRanks() {
600
+ const possible = this.possibleMoves(this.selectedPiece, this.toMove)
601
+ .filter(move => {
602
+ if (move[0] === this.selectedFile) {
603
+ return move[1]
604
+ }
605
+ })
606
+ .map(pos => {
607
+ return pos[1].toString()
608
+ })
226
609
  /** Filter unique */
227
610
  return [...new Set(possible)]
228
611
  }
229
612
 
230
613
  /**
231
- * TODO: Checks if it is a legal move
232
- * If all is well, commits the move
614
+ * 1. Process captures
615
+ * 2. TODO: Check check
616
+ * 3. TODO: Check checkmate
233
617
  */
234
- validateMove () {
235
- const legal = true
236
- // somehow find out if it's a legal move. Then;
237
- if (legal) {
238
- // Commit the move to update the board
239
- this.processCapture()
240
- this.commitMove()
241
- // Opponent to move
242
- } else {
243
- // retry
244
- console.error('Not a legal move. Try again')
245
- this.runPrompt()
246
- }
618
+ async validateMove() {
619
+ // Commit the move to update the board
620
+ this.processCapture()
621
+ await this.promptPromotion()
622
+ this.commitMove()
247
623
  }
248
624
 
249
625
  /**
250
626
  * Allowed configures the check to consider if a capture may be made
627
+ * If it is true,
251
628
  * Returns 'capture' if there is an opposing piece at the given position
629
+ * Returns 'check' if the opposing piece at the given position is a king
252
630
  * Returns 'blocked' if there is an own piece or 'possible' for no piece at the given position
253
631
  */
254
- movePotential (pos, allowed) {
255
- const opponentsPieces = this.board[this.utils.opponentColor(this.toMove)].pieces
256
- const ownPiece = this.board[this.toMove].pieces.find((piece) => {
632
+ movePotential(pos, color, allowed) {
633
+ if (pos[0] === undefined || pos[1] === undefined) {
634
+ return "impossible"
635
+ }
636
+
637
+ const opponentsPieces =
638
+ this.board[this.utils.opposingColor(color)].pieces
639
+
640
+ const ownPiece = this.board[color].pieces.find(piece => {
257
641
  return piece.pos[0] === pos[0] && piece.pos[1] === pos[1]
258
642
  })
259
- const opponentsPiece = opponentsPieces.find((piece) => {
643
+ const opponentsPiece = opponentsPieces.find(piece => {
260
644
  return piece.pos[0] === pos[0] && piece.pos[1] === pos[1]
261
645
  })
262
646
 
263
- if (opponentsPiece && allowed) { return 'capture' }
264
- if (ownPiece || opponentsPiece && !allowed) { return 'blocked' }
265
- if (!ownPiece && !opponentsPiece) { return 'possible' }
647
+ if (opponentsPiece && allowed) {
648
+ if (opponentsPiece.type === "King") {
649
+ return "check"
650
+ } else {
651
+ return "capture"
652
+ }
653
+ }
654
+ if (ownPiece && allowed) {
655
+ return "blocked"
656
+ }
657
+ if (ownPiece || (opponentsPiece && !allowed)) {
658
+ return "blocked"
659
+ }
660
+ if (!ownPiece && !opponentsPiece) {
661
+ return "possible"
662
+ }
266
663
  }
267
664
 
268
665
  /**
@@ -270,16 +667,84 @@ class Game {
270
667
  * 1. Checks for capture
271
668
  * 2. If capture, set the piece status to captured
272
669
  */
273
- processCapture () {
274
- const opponentsPieces = this.board[this.utils.opponentColor(this.toMove)].pieces
275
- const capturedPiece = opponentsPieces.find((piece) => {
276
- return piece.pos[0] === this.selectedFile && piece.pos[1] === this.selectedRank
670
+ processCapture() {
671
+ const opponentsPieces =
672
+ this.board[this.utils.opposingColor(this.toMove)].pieces
673
+ const capturedPiece = opponentsPieces.find(piece => {
674
+ return (
675
+ piece.pos[0] === this.selectedFile && piece.pos[1] === this.selectedRank
676
+ )
277
677
  })
278
678
  if (capturedPiece) {
279
679
  capturedPiece.captured = true
280
680
  }
281
681
  }
282
682
 
683
+ /**
684
+ * Prompt promotion
685
+ * 1. Asks user which piece to promote to
686
+ * 2. Sets said piece to its new values
687
+ */
688
+ async promptPromotion() {
689
+ if (
690
+ (this.selectedPiece.type === "Pawn" &&
691
+ this.toMove === "white" &&
692
+ this.selectedRank === 8) ||
693
+ (this.selectedPiece.type === "Pawn" &&
694
+ this.toMove === "black" &&
695
+ this.selectedRank === 1)
696
+ ) {
697
+ await this.enquirer
698
+ .prompt({
699
+ type: "select",
700
+ name: "promotion",
701
+ message: "Promote to:",
702
+ choices: this.board[this.toMove].promotionChoices.map(choice => {
703
+ return choice.piece
704
+ }),
705
+ result: piece => {
706
+ const promotionChoice = this.board[
707
+ this.toMove
708
+ ].promotionChoices.find(choice => {
709
+ return choice.piece === piece
710
+ })
711
+ const selectedPiece = this.getSelectedPiece()
712
+ selectedPiece.piece = piece
713
+ selectedPiece.type = promotionChoice.type
714
+ selectedPiece.prefix = promotionChoice.prefix
715
+ selectedPiece.value = promotionChoice.piece
716
+ return selectedPiece
717
+ },
718
+ })
719
+ .catch(console.error)
720
+ }
721
+ }
722
+
723
+ /**
724
+ * Check master function. Is run right before passTurn and board.render
725
+ */
726
+ checkForCheck() {
727
+ // If the opponent's checks length is > 0, that means you're in check
728
+ const opponent = this.utils.opposingColor(this.toMove)
729
+ const opponentsChecks = this.allPossibleChecks(opponent)
730
+
731
+ if (opponentsChecks.length > 0) {
732
+ this.board[this.toMove].inCheck = true
733
+ } else {
734
+ this.board[this.toMove].inCheck = false
735
+ this.board[opponent].inCheck = false
736
+ }
737
+ }
738
+
739
+ /**
740
+ * Passes the turn to the other player by setting the toMove var and prompting the next move
741
+ */
742
+ passTurn() {
743
+ this.toMove = this.toMove === "white" ? "black" : "white"
744
+ this.checkForCheck()
745
+ this.promptPieceSelection()
746
+ }
747
+
283
748
  /**
284
749
  * Commits the given move
285
750
  * 1. Sets the position of the selected piece
@@ -287,53 +752,37 @@ class Game {
287
752
  * 3. Passes the turn to the other player
288
753
  * 4. Renders the board
289
754
  */
290
- commitMove () {
755
+ commitMove() {
291
756
  this.getSelectedPiece().pos = [this.selectedFile, this.selectedRank]
292
757
  this.commitToHistory()
293
758
  this.passTurn()
759
+ this.board.clear()
294
760
  this.board.render()
295
761
  }
296
762
 
297
- /**
298
- * Passes the turn to the other player by setting the toMove var and prompting the next move
299
- */
300
- passTurn () {
301
- this.toMove === 'white' ? this.toMove = 'black' : this.toMove = 'white'
302
- this.promptPieceSelection()
303
- }
304
-
305
763
  /**
306
764
  * Pushes move to history
307
765
  */
308
- commitToHistory () {
309
- console.log(`${this.selectedPiece.piece} to ${this.selectedFile}${this.selectedRank}`)
310
- // if (this.toMove === 'white') {
311
- // this.history.push({
312
- // turn: this.moveIndex + 1,
313
- // move: move
314
- // })
315
- // } else {
316
- // this.history[this.moveIndex].move += `, ${move}`
317
- // this.moveIndex++
318
- // }
319
- }
766
+ commitToHistory() {}
320
767
 
321
768
  /**
322
769
  * Returns a list of available pieces for the prompt
323
770
  */
324
- listAvailablePieces () {
325
- return this.board[this.toMove].pieces.filter((piece) => {
326
- return !piece.captured && this.possibleMoves(piece).length > 0
327
- }).map((piece) => {
328
- return `${piece.piece} (${piece.pos})`
329
- })
771
+ listAvailablePieces() {
772
+ return this.board[this.toMove].pieces
773
+ .filter(piece => {
774
+ return !piece.captured && this.possibleMoves(piece, this.toMove).length > 0
775
+ })
776
+ .map(piece => {
777
+ return `${piece.piece} (${piece.pos})`
778
+ })
330
779
  }
331
780
 
332
781
  /**
333
782
  * Sets the given piece as selected for this move
334
783
  */
335
- setSelectedPiece (piece) {
336
- const selection = this.board[this.toMove].pieces.find((p) => {
784
+ setSelectedPiece(piece) {
785
+ const selection = this.board[this.toMove].pieces.find(p => {
337
786
  return p.pos[0] === piece[3] && p.pos[1] === Number(piece[5])
338
787
  })
339
788
  this.selectedPiece = selection
@@ -342,31 +791,54 @@ class Game {
342
791
  /**
343
792
  * Sets the given file as selected for this move
344
793
  */
345
- setSelectedFile (file) {
794
+ setSelectedFile(file) {
346
795
  this.selectedFile = file
347
796
  }
348
797
 
349
798
  /**
350
799
  * Sets the given rank as selected for this move
351
800
  */
352
- setSelectedRank (rank) {
801
+ setSelectedRank(rank) {
353
802
  this.selectedRank = rank
354
803
  }
355
804
 
805
+ /**
806
+ * Get the current player's piece
807
+ */
808
+ getPiece(type) {
809
+ return this.board[this.toMove].pieces.find(piece => {
810
+ return piece.type === type
811
+ })
812
+ }
813
+
814
+ /**
815
+ * Get the opponent's piece
816
+ */
817
+ getOpponentsPiece(type) {
818
+ return this.board[this.utils.opposingColor(this.toMove)].pieces.find(
819
+ piece => {
820
+ return piece.type === type
821
+ }
822
+ )
823
+ }
824
+
356
825
  /**
357
826
  * returns the selected piece for this move
358
827
  */
359
- getSelectedPiece () {
360
- return this.board[this.toMove].pieces.find(piece => { return piece === this.selectedPiece })
828
+ getSelectedPiece() {
829
+ return this.board[this.toMove].pieces.find(piece => {
830
+ return piece === this.selectedPiece
831
+ })
361
832
  }
362
833
 
363
834
  /**
364
835
  * Um Gottes Wille, nicht!
365
836
  */
366
- init () {
837
+ init() {
838
+ this.board.clear()
367
839
  this.board.render()
368
840
  this.promptPieceSelection()
369
841
  }
370
842
  }
371
843
 
372
- module.exports = Game
844
+ module.exports = Game