js-chess-engine 1.0.2 → 2.0.1-rc.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 (103) hide show
  1. package/README.md +586 -205
  2. package/dist/adapters/APIAdapter.d.ts +88 -0
  3. package/dist/adapters/APIAdapter.d.ts.map +1 -0
  4. package/dist/adapters/APIAdapter.js +225 -0
  5. package/dist/adapters/APIAdapter.js.map +1 -0
  6. package/dist/ai/AIEngine.d.ts +42 -0
  7. package/dist/ai/AIEngine.d.ts.map +1 -0
  8. package/dist/ai/AIEngine.js +62 -0
  9. package/dist/ai/AIEngine.js.map +1 -0
  10. package/dist/ai/Evaluator.d.ts +48 -0
  11. package/dist/ai/Evaluator.d.ts.map +1 -0
  12. package/dist/ai/Evaluator.js +248 -0
  13. package/dist/ai/Evaluator.js.map +1 -0
  14. package/dist/ai/MoveOrdering.d.ts +60 -0
  15. package/dist/ai/MoveOrdering.d.ts.map +1 -0
  16. package/dist/ai/MoveOrdering.js +173 -0
  17. package/dist/ai/MoveOrdering.js.map +1 -0
  18. package/dist/ai/Search.d.ts +62 -0
  19. package/dist/ai/Search.d.ts.map +1 -0
  20. package/dist/ai/Search.js +340 -0
  21. package/dist/ai/Search.js.map +1 -0
  22. package/dist/ai/TranspositionTable.d.ts +100 -0
  23. package/dist/ai/TranspositionTable.d.ts.map +1 -0
  24. package/dist/ai/TranspositionTable.js +176 -0
  25. package/dist/ai/TranspositionTable.js.map +1 -0
  26. package/dist/core/AttackDetector.d.ts +52 -0
  27. package/dist/core/AttackDetector.d.ts.map +1 -0
  28. package/dist/core/AttackDetector.js +387 -0
  29. package/dist/core/AttackDetector.js.map +1 -0
  30. package/dist/core/Board.d.ts +109 -0
  31. package/dist/core/Board.d.ts.map +1 -0
  32. package/dist/core/Board.js +410 -0
  33. package/dist/core/Board.js.map +1 -0
  34. package/dist/core/MoveGenerator.d.ts +48 -0
  35. package/dist/core/MoveGenerator.d.ts.map +1 -0
  36. package/dist/core/MoveGenerator.js +678 -0
  37. package/dist/core/MoveGenerator.js.map +1 -0
  38. package/dist/core/Position.d.ts +135 -0
  39. package/dist/core/Position.d.ts.map +1 -0
  40. package/dist/core/Position.js +351 -0
  41. package/dist/core/Position.js.map +1 -0
  42. package/dist/core/index.d.ts +9 -0
  43. package/dist/core/index.d.ts.map +1 -0
  44. package/dist/core/index.js +25 -0
  45. package/dist/core/index.js.map +1 -0
  46. package/dist/core/zobrist.d.ts +93 -0
  47. package/dist/core/zobrist.d.ts.map +1 -0
  48. package/dist/core/zobrist.js +273 -0
  49. package/dist/core/zobrist.js.map +1 -0
  50. package/dist/index.d.ts +154 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +353 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/types/ai.types.d.ts +97 -0
  55. package/dist/types/ai.types.d.ts.map +1 -0
  56. package/dist/types/ai.types.js +17 -0
  57. package/dist/types/ai.types.js.map +1 -0
  58. package/dist/types/board.types.d.ts +140 -0
  59. package/dist/types/board.types.d.ts.map +1 -0
  60. package/dist/types/board.types.js +34 -0
  61. package/dist/types/board.types.js.map +1 -0
  62. package/dist/types/index.d.ts +7 -0
  63. package/dist/types/index.d.ts.map +1 -0
  64. package/dist/types/index.js +26 -0
  65. package/dist/types/index.js.map +1 -0
  66. package/dist/types/move.types.d.ts +70 -0
  67. package/dist/types/move.types.d.ts.map +1 -0
  68. package/dist/types/move.types.js +53 -0
  69. package/dist/types/move.types.js.map +1 -0
  70. package/dist/utils/constants.d.ts +123 -0
  71. package/dist/utils/constants.d.ts.map +1 -0
  72. package/dist/utils/constants.js +259 -0
  73. package/dist/utils/constants.js.map +1 -0
  74. package/dist/utils/conversion.d.ts +152 -0
  75. package/dist/utils/conversion.d.ts.map +1 -0
  76. package/dist/utils/conversion.js +288 -0
  77. package/dist/utils/conversion.js.map +1 -0
  78. package/dist/utils/environment.d.ts +33 -0
  79. package/dist/utils/environment.d.ts.map +1 -0
  80. package/dist/utils/environment.js +71 -0
  81. package/dist/utils/environment.js.map +1 -0
  82. package/dist/utils/fen.d.ts +28 -0
  83. package/dist/utils/fen.d.ts.map +1 -0
  84. package/dist/utils/fen.js +203 -0
  85. package/dist/utils/fen.js.map +1 -0
  86. package/package.json +27 -28
  87. package/.eslintrc.json +0 -16
  88. package/.github/workflows/main.yml +0 -20
  89. package/CHANGELOG.md +0 -587
  90. package/dist/js-chess-engine.js +0 -1
  91. package/example/aiMatch.mjs +0 -21
  92. package/example/console.mjs +0 -37
  93. package/example/server.mjs +0 -27
  94. package/lib/Board.mjs +0 -943
  95. package/lib/const/board.mjs +0 -838
  96. package/lib/js-chess-engine.mjs +0 -99
  97. package/lib/utils.mjs +0 -154
  98. package/test/.eslintrc.json +0 -11
  99. package/test/ai.test.mjs +0 -132
  100. package/test/badge.svg +0 -1
  101. package/test/importExport.mjs +0 -108
  102. package/test/moves.test.mjs +0 -773
  103. package/webpack.config.js +0 -12
package/lib/Board.mjs DELETED
@@ -1,943 +0,0 @@
1
- import {
2
- AI_LEVELS,
3
- AI_DEPTH_BY_LEVEL,
4
- COLORS,
5
- NEW_GAME_BOARD_CONFIG,
6
- NEW_GAME_SETTINGS,
7
- down,
8
- downByColor,
9
- downLeft,
10
- downLeftByColor,
11
- downLeftDown,
12
- downLeftLeft,
13
- downRight,
14
- downRightByColor,
15
- downRightDown,
16
- downRightRight,
17
- left,
18
- right,
19
- scoreByPosition,
20
- up,
21
- upByColor,
22
- upLeft,
23
- upLeftByColor,
24
- upLeftLeft,
25
- upLeftUp,
26
- upRight,
27
- upRightByColor,
28
- upRightRight,
29
- upRightUp,
30
- } from './const/board.mjs'
31
-
32
- import { getPieceValue, getJSONfromFEN, isPieceValid, isLocationValid } from './utils.mjs'
33
-
34
- const SCORE = {
35
- MIN: -1000,
36
- MAX: 1000,
37
- }
38
-
39
- const PIECE_VALUE_MULTIPLIER = 10
40
-
41
- export default class Board {
42
- constructor (configuration = JSON.parse(JSON.stringify(NEW_GAME_BOARD_CONFIG))) {
43
- if (typeof configuration === 'object') {
44
- this.configuration = Object.assign({}, NEW_GAME_SETTINGS, configuration)
45
- } else if (typeof configuration === 'string') {
46
- this.configuration = Object.assign({}, NEW_GAME_SETTINGS, getJSONfromFEN(configuration))
47
- } else {
48
- throw new Error(`Unknown configuration type ${typeof config}.`)
49
- }
50
- if (!this.configuration.castling) {
51
- this.configuration.castling = {
52
- whiteShort: true,
53
- blackShort: true,
54
- whiteLong: true,
55
- blackLong: true,
56
- }
57
- }
58
- this.history = []
59
- }
60
-
61
- getAttackingFields (color = this.getPlayingColor()) {
62
- let attackingFields = []
63
- for (const location in this.configuration.pieces) {
64
- const piece = this.getPiece(location)
65
- if (this.getPieceColor(piece) === color) {
66
- attackingFields = [...attackingFields, ...this.getPieceMoves(piece, location)]
67
- }
68
- }
69
- return attackingFields
70
- }
71
-
72
- isAttackingKing (color = this.getPlayingColor()) {
73
- let kingLocation = null
74
- for (const location in this.configuration.pieces) {
75
- const piece = this.getPiece(location)
76
- if (this.isKing(piece) && this.getPieceColor(piece) !== color) {
77
- kingLocation = location
78
- break
79
- }
80
- }
81
-
82
- return this.isPieceUnderAttack(kingLocation)
83
- }
84
-
85
- isPieceUnderAttack (pieceLocation) {
86
- const playerColor = this.getPieceOnLocationColor(pieceLocation)
87
- const enemyColor = this.getEnemyColor(playerColor)
88
- let isUnderAttack = false
89
-
90
- let field = pieceLocation
91
- let distance = 0
92
- while (up(field) && !isUnderAttack) {
93
- field = up(field)
94
- distance++
95
- const piece = this.getPiece(field)
96
- if (piece && this.getPieceColor(piece) === enemyColor &&
97
- (this.isRook(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) {
98
- isUnderAttack = true
99
- }
100
- if (piece) break
101
- }
102
-
103
- field = pieceLocation
104
- distance = 0
105
- while (down(field) && !isUnderAttack) {
106
- field = down(field)
107
- distance++
108
- const piece = this.getPiece(field)
109
- if (piece && this.getPieceColor(piece) === enemyColor &&
110
- (this.isRook(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) {
111
- isUnderAttack = true
112
- }
113
- if (piece) break
114
- }
115
-
116
- field = pieceLocation
117
- distance = 0
118
- while (left(field) && !isUnderAttack) {
119
- field = left(field)
120
- distance++
121
- const piece = this.getPiece(field)
122
- if (piece && this.getPieceColor(piece) === enemyColor &&
123
- (this.isRook(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) {
124
- isUnderAttack = true
125
- }
126
- if (piece) break
127
- }
128
-
129
- field = pieceLocation
130
- distance = 0
131
- while (right(field) && !isUnderAttack) {
132
- field = right(field)
133
- distance++
134
- const piece = this.getPiece(field)
135
- if (piece && this.getPieceColor(piece) === enemyColor &&
136
- (this.isRook(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) {
137
- isUnderAttack = true
138
- }
139
- if (piece) break
140
- }
141
-
142
- field = pieceLocation
143
- distance = 0
144
- while (upRightByColor(field, playerColor) && !isUnderAttack) {
145
- field = upRightByColor(field, playerColor)
146
- distance++
147
- const piece = this.getPiece(field)
148
- if (piece && this.getPieceColor(piece) === enemyColor &&
149
- (this.isBishop(piece) || this.isQueen(piece) || (distance === 1 && (this.isKing(piece) || this.isPawn(piece))))) {
150
- isUnderAttack = true
151
- }
152
- if (piece) break
153
- }
154
-
155
- field = pieceLocation
156
- distance = 0
157
- while (upLeftByColor(field, playerColor) && !isUnderAttack) {
158
- field = upLeftByColor(field, playerColor)
159
- distance++
160
- const piece = this.getPiece(field)
161
- if (piece && this.getPieceColor(piece) === enemyColor &&
162
- (this.isBishop(piece) || this.isQueen(piece) || (distance === 1 && (this.isKing(piece) || this.isPawn(piece))))) {
163
- isUnderAttack = true
164
- }
165
- if (piece) break
166
- }
167
-
168
- field = pieceLocation
169
- distance = 0
170
- while (downRightByColor(field, playerColor) && !isUnderAttack) {
171
- field = downRightByColor(field, playerColor)
172
- distance++
173
- const piece = this.getPiece(field)
174
- if (piece && this.getPieceColor(piece) === enemyColor &&
175
- (this.isBishop(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) {
176
- isUnderAttack = true
177
- }
178
- if (piece) break
179
- }
180
-
181
- field = pieceLocation
182
- distance = 0
183
- while (downLeftByColor(field, playerColor) && !isUnderAttack) {
184
- field = downLeftByColor(field, playerColor)
185
- distance++
186
- const piece = this.getPiece(field)
187
- if (piece && this.getPieceColor(piece) === enemyColor &&
188
- (this.isBishop(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) {
189
- isUnderAttack = true
190
- }
191
- if (piece) break
192
- }
193
-
194
- field = upRightUp(pieceLocation)
195
- let piece = this.getPiece(field)
196
- if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) {
197
- isUnderAttack = true
198
- }
199
- field = upRightRight(pieceLocation)
200
- piece = this.getPiece(field)
201
- if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) {
202
- isUnderAttack = true
203
- }
204
- field = upLeftLeft(pieceLocation)
205
- piece = this.getPiece(field)
206
- if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) {
207
- isUnderAttack = true
208
- }
209
- field = upLeftUp(pieceLocation)
210
- piece = this.getPiece(field)
211
- if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) {
212
- isUnderAttack = true
213
- }
214
- field = downLeftDown(pieceLocation)
215
- piece = this.getPiece(field)
216
- if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) {
217
- isUnderAttack = true
218
- }
219
- field = downLeftLeft(pieceLocation)
220
- piece = this.getPiece(field)
221
- if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) {
222
- isUnderAttack = true
223
- }
224
- field = downRightDown(pieceLocation)
225
- piece = this.getPiece(field)
226
- if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) {
227
- isUnderAttack = true
228
- }
229
- field = downRightRight(pieceLocation)
230
- piece = this.getPiece(field)
231
- if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) {
232
- isUnderAttack = true
233
- }
234
-
235
- return isUnderAttack
236
- }
237
-
238
- hasPlayingPlayerCheck () {
239
- return this.isAttackingKing(this.getNonPlayingColor())
240
- }
241
-
242
- hasNonPlayingPlayerCheck () {
243
- return this.isAttackingKing(this.getPlayingColor())
244
- }
245
-
246
- getLowestValuePieceAttackingLocation (location, color = this.getPlayingColor()) {
247
- let pieceValue = null
248
- for (const field in this.configuration.pieces) {
249
- const piece = this.getPiece(field)
250
- if (this.getPieceColor(piece) === color) {
251
- this.getPieceMoves(piece, field).map(attackingLocation => {
252
- if (attackingLocation === location && (pieceValue === null || getPieceValue(piece) < pieceValue)) {
253
- pieceValue = getPieceValue(piece)
254
- }
255
- })
256
- }
257
- }
258
- return pieceValue
259
- }
260
-
261
- getMoves (color = this.getPlayingColor(), movablePiecesRequiredToSkipTest = null) {
262
- const allMoves = {}
263
- let movablePiecesCount = 0
264
- for (const location in this.configuration.pieces) {
265
- const piece = this.getPiece(location)
266
- if (this.getPieceColor(piece) === color) {
267
- const moves = this.getPieceMoves(piece, location)
268
- if (moves.length) {
269
- movablePiecesCount++
270
- }
271
- Object.assign(allMoves, { [location]: moves })
272
- }
273
- }
274
-
275
- const enemyAttackingFields = this.getAttackingFields(this.getNonPlayingColor())
276
- if (this.isLeftCastlingPossible(enemyAttackingFields)) {
277
- if (this.isPlayingWhite()) allMoves.E1.push('C1')
278
- if (this.isPlayingBlack()) allMoves.E8.push('C8')
279
- }
280
- if (this.isRightCastlingPossible(enemyAttackingFields)) {
281
- if (this.isPlayingWhite()) allMoves.E1.push('G1')
282
- if (this.isPlayingBlack()) allMoves.E8.push('G8')
283
- }
284
-
285
- if (movablePiecesRequiredToSkipTest && movablePiecesCount > movablePiecesRequiredToSkipTest) return allMoves
286
-
287
- const moves = {}
288
- for (const from in allMoves) {
289
- allMoves[from].map(to => {
290
- const testConfiguration = {
291
- pieces: Object.assign({}, this.configuration.pieces),
292
- castling: Object.assign({}, this.configuration.castling),
293
- }
294
- const testBoard = new Board(testConfiguration)
295
- testBoard.move(from, to)
296
- if (
297
- (this.isPlayingWhite() && !testBoard.isAttackingKing(COLORS.BLACK)) ||
298
- (this.isPlayingBlack() && !testBoard.isAttackingKing(COLORS.WHITE))
299
- ) {
300
- if (!moves[from]) {
301
- moves[from] = []
302
- }
303
- moves[from].push(to)
304
- }
305
- })
306
- }
307
-
308
- if (!Object.keys(moves).length) {
309
- this.configuration.isFinished = true
310
- if (this.hasPlayingPlayerCheck()) {
311
- this.configuration.checkMate = true
312
- }
313
- }
314
-
315
- return moves
316
- }
317
-
318
- isLeftCastlingPossible (enemyAttackingFields) {
319
- if (this.isPlayingWhite() && !this.configuration.castling.whiteLong) return false
320
- if (this.isPlayingBlack() && !this.configuration.castling.blackLong) return false
321
-
322
- let kingLocation = null
323
- if (this.isPlayingWhite() && this.getPiece('E1') === 'K' && this.getPiece('A1') === 'R' && !enemyAttackingFields.includes('E1')) {
324
- kingLocation = 'E1'
325
- } else if (this.isPlayingBlack() && this.getPiece('E8') === 'k' && this.getPiece('A8') === 'r' && !enemyAttackingFields.includes('E8')) {
326
- kingLocation = 'E8'
327
- }
328
- if (!kingLocation) return false
329
- let field = left(kingLocation)
330
- if (this.getPiece(field) || enemyAttackingFields.includes(field)) return false
331
- field = left(field)
332
- if (this.getPiece(field) || enemyAttackingFields.includes(field)) return false
333
- field = left(field)
334
- if (this.getPiece(field)) return false
335
-
336
- return true
337
- }
338
-
339
- isRightCastlingPossible (enemyAttackingFields) {
340
- if (this.isPlayingWhite() && !this.configuration.castling.whiteShort) return false
341
- if (this.isPlayingBlack() && !this.configuration.castling.blackShort) return false
342
-
343
- let kingLocation = null
344
- if (this.isPlayingWhite() && this.getPiece('E1') === 'K' && this.getPiece('H1') === 'R' && !enemyAttackingFields.includes('E1')) {
345
- kingLocation = 'E1'
346
- } else if (this.isPlayingBlack() && this.getPiece('E8') === 'k' && this.getPiece('H8') === 'r' && !enemyAttackingFields.includes('E8')) {
347
- kingLocation = 'E8'
348
- }
349
- if (!kingLocation) return false
350
-
351
- let field = right(kingLocation)
352
- if (this.getPiece(field) || enemyAttackingFields.includes(field)) return false
353
- field = right(field)
354
- if (this.getPiece(field) || enemyAttackingFields.includes(field)) return false
355
-
356
- return true
357
- }
358
-
359
- getPieceMoves (piece, location) {
360
- if (this.isPawn(piece)) return this.getPawnMoves(piece, location)
361
- if (this.isKnight(piece)) return this.getKnightMoves(piece, location)
362
- if (this.isRook(piece)) return this.getRookMoves(piece, location)
363
- if (this.isBishop(piece)) return this.getBishopMoves(piece, location)
364
- if (this.isQueen(piece)) return this.getQueenMoves(piece, location)
365
- if (this.isKing(piece)) return this.getKingMoves(piece, location)
366
- return []
367
- }
368
-
369
- isPawn (piece) {
370
- return piece.toUpperCase() === 'P'
371
- }
372
-
373
- isKnight (piece) {
374
- return piece.toUpperCase() === 'N'
375
- }
376
-
377
- isRook (piece) {
378
- return piece.toUpperCase() === 'R'
379
- }
380
-
381
- isBishop (piece) {
382
- return piece.toUpperCase() === 'B'
383
- }
384
-
385
- isQueen (piece) {
386
- return piece.toUpperCase() === 'Q'
387
- }
388
-
389
- isKing (piece) {
390
- return piece.toUpperCase() === 'K'
391
- }
392
-
393
- getPawnMoves (piece, location) {
394
- const moves = []
395
- const color = this.getPieceColor(piece)
396
- let move = upByColor(location, color)
397
-
398
- if (move && !this.getPiece(move)) {
399
- moves.push(move)
400
- move = upByColor(move, color)
401
- if (isInStartLine(color, location) && move && !this.getPiece(move)) {
402
- moves.push(move)
403
- }
404
- }
405
-
406
- move = upLeftByColor(location, color)
407
- if (move && ((this.getPiece(move) && this.getPieceOnLocationColor(move) !== color) || (move === this.configuration.enPassant))) {
408
- moves.push(move)
409
- }
410
-
411
- move = upRightByColor(location, color)
412
- if (move && ((this.getPiece(move) && this.getPieceOnLocationColor(move) !== color) || (move === this.configuration.enPassant))) {
413
- moves.push(move)
414
- }
415
-
416
- function isInStartLine (color, location) {
417
- if (color === COLORS.WHITE && location[1] === '2') {
418
- return true
419
- }
420
- if (color === COLORS.BLACK && location[1] === '7') {
421
- return true
422
- }
423
- return false
424
- }
425
-
426
- return moves
427
- }
428
-
429
- getKnightMoves (piece, location) {
430
- const moves = []
431
- const color = this.getPieceColor(piece)
432
-
433
- let field = upRightUp(location)
434
- if (field && this.getPieceOnLocationColor(field) !== color) {
435
- moves.push(field)
436
- }
437
-
438
- field = upRightRight(location)
439
- if (field && this.getPieceOnLocationColor(field) !== color) {
440
- moves.push(field)
441
- }
442
-
443
- field = upLeftUp(location)
444
- if (field && this.getPieceOnLocationColor(field) !== color) {
445
- moves.push(field)
446
- }
447
-
448
- field = upLeftLeft(location)
449
- if (field && this.getPieceOnLocationColor(field) !== color) {
450
- moves.push(field)
451
- }
452
-
453
- field = downLeftLeft(location)
454
- if (field && this.getPieceOnLocationColor(field) !== color) {
455
- moves.push(field)
456
- }
457
-
458
- field = downLeftDown(location)
459
- if (field && this.getPieceOnLocationColor(field) !== color) {
460
- moves.push(field)
461
- }
462
-
463
- field = downRightRight(location)
464
- if (field && this.getPieceOnLocationColor(field) !== color) {
465
- moves.push(field)
466
- }
467
-
468
- field = downRightDown(location)
469
- if (field && this.getPieceOnLocationColor(field) !== color) {
470
- moves.push(field)
471
- }
472
-
473
- return moves
474
- }
475
-
476
- getRookMoves (piece, location) {
477
- const moves = []
478
- const color = this.getPieceColor(piece)
479
-
480
- let field = location
481
- while (up(field)) {
482
- field = up(field)
483
- const pieceOnFieldColor = this.getPieceOnLocationColor(field)
484
- if (this.getPieceOnLocationColor(field) !== color) {
485
- moves.push(field)
486
- }
487
- if (pieceOnFieldColor) break
488
- }
489
-
490
- field = location
491
- while (down(field)) {
492
- field = down(field)
493
- const pieceOnFieldColor = this.getPieceOnLocationColor(field)
494
- if (this.getPieceOnLocationColor(field) !== color) {
495
- moves.push(field)
496
- }
497
- if (pieceOnFieldColor) break
498
- }
499
-
500
- field = location
501
- while (right(field)) {
502
- field = right(field)
503
- const pieceOnFieldColor = this.getPieceOnLocationColor(field)
504
- if (this.getPieceOnLocationColor(field) !== color) {
505
- moves.push(field)
506
- }
507
- if (pieceOnFieldColor) break
508
- }
509
-
510
- field = location
511
- while (left(field)) {
512
- field = left(field)
513
- const pieceOnFieldColor = this.getPieceOnLocationColor(field)
514
- if (this.getPieceOnLocationColor(field) !== color) {
515
- moves.push(field)
516
- }
517
- if (pieceOnFieldColor) break
518
- }
519
-
520
- return moves
521
- }
522
-
523
- getBishopMoves (piece, location) {
524
- const moves = []
525
- const color = this.getPieceColor(piece)
526
-
527
- let field = location
528
- while (upLeft(field)) {
529
- field = upLeft(field)
530
- const pieceOnFieldColor = this.getPieceOnLocationColor(field)
531
- if (this.getPieceOnLocationColor(field) !== color) {
532
- moves.push(field)
533
- }
534
- if (pieceOnFieldColor) break
535
- }
536
-
537
- field = location
538
- while (upRight(field)) {
539
- field = upRight(field)
540
- const pieceOnFieldColor = this.getPieceOnLocationColor(field)
541
- if (this.getPieceOnLocationColor(field) !== color) {
542
- moves.push(field)
543
- }
544
- if (pieceOnFieldColor) break
545
- }
546
-
547
- field = location
548
- while (downLeft(field)) {
549
- field = downLeft(field)
550
- const pieceOnFieldColor = this.getPieceOnLocationColor(field)
551
- if (this.getPieceOnLocationColor(field) !== color) {
552
- moves.push(field)
553
- }
554
- if (pieceOnFieldColor) break
555
- }
556
-
557
- field = location
558
- while (downRight(field)) {
559
- field = downRight(field)
560
- const pieceOnFieldColor = this.getPieceOnLocationColor(field)
561
- if (this.getPieceOnLocationColor(field) !== color) {
562
- moves.push(field)
563
- }
564
- if (pieceOnFieldColor) break
565
- }
566
-
567
- return moves
568
- }
569
-
570
- getQueenMoves (piece, location) {
571
- const moves = [
572
- ...this.getRookMoves(piece, location),
573
- ...this.getBishopMoves(piece, location),
574
- ]
575
- return moves
576
- }
577
-
578
- getKingMoves (piece, location) {
579
- const moves = []
580
- const color = this.getPieceColor(piece)
581
-
582
- let field = location
583
- field = up(field)
584
- if (field && this.getPieceOnLocationColor(field) !== color) {
585
- moves.push(field)
586
- }
587
-
588
- field = location
589
- field = right(field)
590
- if (field && this.getPieceOnLocationColor(field) !== color) {
591
- moves.push(field)
592
- }
593
-
594
- field = location
595
- field = down(field)
596
- if (field && this.getPieceOnLocationColor(field) !== color) {
597
- moves.push(field)
598
- }
599
-
600
- field = location
601
- field = left(field)
602
- if (field && this.getPieceOnLocationColor(field) !== color) {
603
- moves.push(field)
604
- }
605
-
606
- field = location
607
- field = upLeft(field)
608
- if (field && this.getPieceOnLocationColor(field) !== color) {
609
- moves.push(field)
610
- }
611
-
612
- field = location
613
- field = upRight(field)
614
- if (field && this.getPieceOnLocationColor(field) !== color) {
615
- moves.push(field)
616
- }
617
-
618
- field = location
619
- field = downLeft(field)
620
- if (field && this.getPieceOnLocationColor(field) !== color) {
621
- moves.push(field)
622
- }
623
-
624
- field = location
625
- field = downRight(field)
626
- if (field && this.getPieceOnLocationColor(field) !== color) {
627
- moves.push(field)
628
- }
629
-
630
- return moves
631
- }
632
-
633
- getPieceColor (piece) {
634
- if (piece.toUpperCase() === piece) return COLORS.WHITE
635
- return COLORS.BLACK
636
- }
637
-
638
- getPieceOnLocationColor (location) {
639
- const piece = this.getPiece(location)
640
- if (!piece) return null
641
- if (piece.toUpperCase() === piece) return COLORS.WHITE
642
- return COLORS.BLACK
643
- }
644
-
645
- getPiece (location) {
646
- return this.configuration.pieces[location]
647
- }
648
-
649
- setPiece (location, piece) {
650
- if (!isPieceValid(piece)) {
651
- throw new Error(`Invalid piece ${piece}`)
652
- }
653
-
654
- if (!isLocationValid(location)) {
655
- throw new Error(`Invalid location ${location}`)
656
- }
657
-
658
- this.configuration.pieces[location.toUpperCase()] = piece
659
- }
660
-
661
- removePiece (location) {
662
- if (!isLocationValid(location)) {
663
- throw new Error(`Invalid location ${location}`)
664
- }
665
-
666
- delete this.configuration.pieces[location.toUpperCase()]
667
- }
668
-
669
- isEmpty (location) {
670
- if (!isLocationValid(location)) {
671
- throw new Error(`Invalid location ${location}`)
672
- }
673
-
674
- return !this.configuration.pieces[location.toUpperCase()]
675
- }
676
-
677
- getEnemyColor (playerColor) {
678
- return playerColor === COLORS.WHITE ? COLORS.BLACK : COLORS.WHITE
679
- }
680
-
681
- getPlayingColor () {
682
- return this.configuration.turn
683
- }
684
-
685
- getNonPlayingColor () {
686
- return this.isPlayingWhite() ? COLORS.BLACK : COLORS.WHITE
687
- }
688
-
689
- isPlayingWhite () {
690
- return this.configuration.turn === COLORS.WHITE
691
- }
692
-
693
- isPlayingBlack () {
694
- return this.configuration.turn === COLORS.BLACK
695
- }
696
-
697
- addMoveToHistory (from, to) {
698
- this.history.push({ from, to, configuration: JSON.parse(JSON.stringify(this.configuration)) })
699
- }
700
-
701
- move (from, to) {
702
- // Move logic
703
- const chessmanFrom = this.getPiece(from)
704
- const chessmanTo = this.getPiece(to)
705
-
706
- if (!chessmanFrom) {
707
- throw new Error(`There is no piece at ${from}`)
708
- }
709
-
710
- Object.assign(this.configuration.pieces, { [to]: chessmanFrom })
711
- delete this.configuration.pieces[from]
712
-
713
- // pawn reaches an end of a chessboard
714
- if (this.isPlayingWhite() && this.isPawn(chessmanFrom) && to[1] === '8') {
715
- Object.assign(this.configuration.pieces, { [to]: 'Q' })
716
- }
717
- if (this.isPlayingBlack() && this.isPawn(chessmanFrom) && to[1] === '1') {
718
- Object.assign(this.configuration.pieces, { [to]: 'q' })
719
- }
720
-
721
- // En passant check
722
- if (this.isPawn(chessmanFrom) && to === this.configuration.enPassant) {
723
- delete this.configuration.pieces[downByColor(to, this.getPlayingColor())]
724
- }
725
-
726
- // pawn En passant special move history
727
- if (this.isPawn(chessmanFrom) && this.isPlayingWhite() && from[1] === '2' && to[1] === '4') {
728
- this.configuration.enPassant = `${from[0]}3`
729
- } else if (this.isPawn(chessmanFrom) && this.isPlayingBlack() && from[1] === '7' && to[1] === '5') {
730
- this.configuration.enPassant = `${from[0]}6`
731
- } else {
732
- this.configuration.enPassant = null
733
- }
734
-
735
- // Castling - disabling
736
- if (from === 'E1') {
737
- Object.assign(this.configuration.castling, { whiteLong: false, whiteShort: false })
738
- }
739
- if (from === 'E8') {
740
- Object.assign(this.configuration.castling, { blackLong: false, blackShort: false })
741
- }
742
- if (from === 'A1') {
743
- Object.assign(this.configuration.castling, { whiteLong: false })
744
- }
745
- if (from === 'H1') {
746
- Object.assign(this.configuration.castling, { whiteShort: false })
747
- }
748
- if (from === 'A8') {
749
- Object.assign(this.configuration.castling, { blackLong: false })
750
- }
751
- if (from === 'H8') {
752
- Object.assign(this.configuration.castling, { blackShort: false })
753
- }
754
-
755
- // Castling - rook is moving too
756
- if (this.isKing(chessmanFrom)) {
757
- if (from === 'E1' && to === 'C1') return this.move('A1', 'D1')
758
- if (from === 'E8' && to === 'C8') return this.move('A8', 'D8')
759
- if (from === 'E1' && to === 'G1') return this.move('H1', 'F1')
760
- if (from === 'E8' && to === 'G8') return this.move('H8', 'F8')
761
- }
762
-
763
- this.configuration.turn = this.isPlayingWhite() ? COLORS.BLACK : COLORS.WHITE
764
-
765
- if (this.isPlayingWhite()) {
766
- this.configuration.fullMove++
767
- }
768
-
769
- this.configuration.halfMove++
770
- if (chessmanTo || this.isPawn(chessmanFrom)) {
771
- this.configuration.halfMove = 0
772
- }
773
- }
774
-
775
- exportJson () {
776
- return {
777
- moves: this.getMoves(),
778
- pieces: this.configuration.pieces,
779
- turn: this.configuration.turn,
780
- isFinished: this.configuration.isFinished,
781
- check: this.hasPlayingPlayerCheck(),
782
- checkMate: this.configuration.checkMate,
783
- castling: this.configuration.castling,
784
- enPassant: this.configuration.enPassant,
785
- halfMove: this.configuration.halfMove,
786
- fullMove: this.configuration.fullMove,
787
- }
788
- }
789
-
790
- calculateAiMove (level) {
791
- const scores = this.calculateAiMoves(level)
792
- return scores[0]
793
- }
794
-
795
- calculateAiMoves (level) {
796
- level = parseInt(level)
797
- if (!AI_LEVELS.includes(level)) {
798
- throw new Error(`Invalid level ${level}. You can choose ${AI_LEVELS.join(',')}`)
799
- }
800
- if (this.shouldIncreaseLevel()) {
801
- level++
802
- }
803
- const scoreTable = []
804
- const initialScore = this.calculateScore(this.getPlayingColor())
805
- const moves = this.getMoves()
806
- for (const from in moves) {
807
- moves[from].map(to => {
808
- const testBoard = this.getTestBoard()
809
- const wasScoreChanged = Boolean(testBoard.getPiece(to))
810
- testBoard.move(from, to)
811
- scoreTable.push({
812
- from,
813
- to,
814
- score: testBoard.testMoveScores(this.getPlayingColor(), level, wasScoreChanged, wasScoreChanged ? testBoard.calculateScore(this.getPlayingColor()) : initialScore, to).score +
815
- testBoard.calculateScoreByPiecesLocation(this.getPlayingColor()) +
816
- (Math.floor(Math.random() * (this.configuration.halfMove > 10 ? this.configuration.halfMove - 10 : 1) * 10) / 10),
817
- })
818
- })
819
- }
820
-
821
- scoreTable.sort((previous, next) => {
822
- return previous.score < next.score ? 1 : previous.score > next.score ? -1 : 0
823
- })
824
- return scoreTable
825
- }
826
-
827
- shouldIncreaseLevel () {
828
- return this.getIngamePiecesValue() < 50
829
- }
830
-
831
- getIngamePiecesValue () {
832
- let scoreIndex = 0
833
- for (const location in this.configuration.pieces) {
834
- const piece = this.getPiece(location)
835
- scoreIndex += getPieceValue(piece)
836
- }
837
- return scoreIndex
838
- }
839
-
840
- getTestBoard () {
841
- const testConfiguration = {
842
- pieces: Object.assign({}, this.configuration.pieces),
843
- castling: Object.assign({}, this.configuration.castling),
844
- turn: this.configuration.turn,
845
- enPassant: this.configuration.enPassant,
846
- }
847
- return new Board(testConfiguration)
848
- }
849
-
850
- testMoveScores (playingPlayerColor, level, capture, initialScore, move, depth = 1) {
851
- let nextMoves = null
852
- if (depth < AI_DEPTH_BY_LEVEL.EXTENDED[level] && this.hasPlayingPlayerCheck()) {
853
- nextMoves = this.getMoves(this.getPlayingColor())
854
- } else if (depth < AI_DEPTH_BY_LEVEL.BASE[level] || (capture && depth < AI_DEPTH_BY_LEVEL.EXTENDED[level])) {
855
- nextMoves = this.getMoves(this.getPlayingColor(), 5)
856
- }
857
-
858
- if (this.configuration.isFinished) {
859
- return {
860
- score: this.calculateScore(playingPlayerColor) + (this.getPlayingColor() === playingPlayerColor ? depth : -depth),
861
- max: true,
862
- }
863
- }
864
-
865
- if (!nextMoves) {
866
- if (initialScore !== null) return { score: initialScore, max: false }
867
- const score = this.calculateScore(playingPlayerColor)
868
- return {
869
- score,
870
- max: false,
871
- }
872
- }
873
-
874
- let bestScore = this.getPlayingColor() === playingPlayerColor ? SCORE.MIN : SCORE.MAX
875
- let maxValueReached = false
876
- for (const from in nextMoves) {
877
- if (maxValueReached) continue
878
- nextMoves[from].map(to => {
879
- if (maxValueReached) return
880
- const testBoard = this.getTestBoard()
881
- const wasScoreChanged = Boolean(testBoard.getPiece(to))
882
- testBoard.move(from, to)
883
- if (testBoard.hasNonPlayingPlayerCheck()) return
884
- const result = testBoard.testMoveScores(playingPlayerColor, level, wasScoreChanged, wasScoreChanged ? testBoard.calculateScore(playingPlayerColor) : initialScore, to, depth + 1)
885
- if (result.max) {
886
- maxValueReached = true
887
- }
888
- if (this.getPlayingColor() === playingPlayerColor) {
889
- bestScore = Math.max(bestScore, result.score)
890
- } else {
891
- bestScore = Math.min(bestScore, result.score)
892
- }
893
- })
894
- }
895
-
896
- return { score: bestScore, max: false }
897
- }
898
-
899
- calculateScoreByPiecesLocation (player = this.getPlayingColor()) {
900
- const columnMapping = { A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7 }
901
- const scoreMultiplier = 0.5
902
- let score = 0
903
- for (const location in this.configuration.pieces) {
904
- const piece = this.getPiece(location)
905
- if (scoreByPosition[piece]) {
906
- const scoreIndex = scoreByPosition[piece][location[1] - 1][columnMapping[location[0]]]
907
- score += (this.getPieceColor(piece) === player ? scoreIndex : -scoreIndex) * scoreMultiplier
908
- }
909
- }
910
- return score
911
- }
912
-
913
- calculateScore (playerColor = this.getPlayingColor()) {
914
- let scoreIndex = 0
915
-
916
- if (this.configuration.checkMate) {
917
- if (this.getPlayingColor() === playerColor) {
918
- return SCORE.MIN
919
- } else {
920
- return SCORE.MAX
921
- }
922
- }
923
-
924
- if (this.configuration.isFinished) {
925
- if (this.getPlayingColor() === playerColor) {
926
- return SCORE.MAX
927
- } else {
928
- return SCORE.MIN
929
- }
930
- }
931
-
932
- for (const location in this.configuration.pieces) {
933
- const piece = this.getPiece(location)
934
- if (this.getPieceColor(piece) === playerColor) {
935
- scoreIndex += getPieceValue(piece) * PIECE_VALUE_MULTIPLIER
936
- } else {
937
- scoreIndex -= getPieceValue(piece) * PIECE_VALUE_MULTIPLIER
938
- }
939
- }
940
-
941
- return scoreIndex
942
- }
943
- }