ychess is a chess implementation written in nim.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

806 lines
27 KiB

  1. import tables
  2. from strutils import parseInt
  3. import algorithm
  4. type
  5. Color* = enum
  6. Black = -1, White = 1
  7. ## Board that saves the board
  8. Board* = array[0..119, int]
  9. ## Board that checks if pieces moved
  10. Moved* = array[0..119, bool]
  11. ## Castle rights for each player
  12. CastleRights = tuple
  13. wk: bool
  14. wq: bool
  15. bk: bool
  16. bq: bool
  17. ## Game as object of different values
  18. Game* = object
  19. board*: Board
  20. moved: Moved
  21. toMove*: Color
  22. previousBoard*: seq[Board]
  23. previousCastleRights*: seq[CastleRights]
  24. ## Move as object
  25. Move* = object
  26. start: int
  27. dest: int
  28. color: Color
  29. prom: int
  30. ## Amount of pieces of a player
  31. Pieces = tuple
  32. p: int
  33. k: int
  34. b: int
  35. r: int
  36. q: int
  37. const
  38. # IDs for piece
  39. BlockID* = 999
  40. PawnID* = 1
  41. KnightID* = 2
  42. BishopID* = 3
  43. RookID* = 4
  44. QueenID* = 5
  45. KingID* = 6
  46. EnPassantID* = 7
  47. # IDs that are saved in the array
  48. Block* = BlockID
  49. WPawn* = PawnID
  50. WKnight* = KnightID
  51. WBishop* = BishopID
  52. WRook* = RookID
  53. WQueen* = QueenID
  54. WKing* = KingID
  55. WEnPassant* = EnPassantID
  56. BPawn* = -PawnID
  57. BKnight* = -KnightID
  58. BBishop* = -BishopID
  59. BRook* = -RookID
  60. BQueen* = -QueenID
  61. BKing* = -KingID
  62. BEnPassant* = EnPassantID
  63. # Directions of movement
  64. N = 10
  65. S = -N
  66. W = 1
  67. E = -W
  68. # Movement options for pieces (Bishop/Rook/Queen can repeat in the same direction)
  69. Knight_Moves = [N+N+E, N+N+W, E+E+N, E+E+S, S+S+E, S+S+W, W+W+N, W+W+S]
  70. Bishop_Moves = [N+E, N+W, S+E, S+W]
  71. Rook_Moves = [N, E, S, W]
  72. Queen_Moves = [N, E, S, W, N+E, N+W, S+E, S+W]
  73. King_Moves = [N, E, S, W, N+E, N+W, S+E, S+W]
  74. King_Moves_White_Castle = [E+E, W+W]
  75. Pawn_Moves_White = [N]
  76. Pawn_Moves_White_Double = [N+N]
  77. Pawn_Moves_White_Attack = [N+E, N+W]
  78. let PieceChar = {
  79. 0: " ",
  80. 1: "P",
  81. 2: "N",
  82. 3: "B",
  83. 4: "R",
  84. 5: "Q",
  85. 6: "K",
  86. 7: " ",
  87. -1: "p",
  88. -2: "n",
  89. -3: "b",
  90. -4: "r",
  91. -5: "q",
  92. -6: "k",
  93. -7: " ",
  94. 999: "-"
  95. }.newTable
  96. let FileChar = {
  97. "a": 7,
  98. "b": 6,
  99. "c": 5,
  100. "d": 4,
  101. "e": 3,
  102. "f": 2,
  103. "g": 1,
  104. "h": 0
  105. }.newTable
  106. const InsufficientMaterial = @[
  107. #p, n, b, r, q
  108. # lone kings
  109. (0, 0, 0, 0, 0),
  110. # knight only
  111. (0, 0, 1, 0, 0),
  112. # bishop only
  113. (0, 1, 0, 0, 0),
  114. # 2 knights
  115. (0, 2, 0, 0, 0)
  116. ]
  117. proc getField*(board: Board, field: int): int =
  118. return board[field]
  119. proc setField(board: var Board, field: int, val: int): bool {.discardable.} =
  120. if (val in PieceChar):
  121. try:
  122. board[field] = val
  123. return true
  124. except Exception:
  125. return false
  126. proc getField*(moved: Moved, field: int): bool =
  127. return moved[field]
  128. proc setField(moved: var Moved, field: int, val: bool): bool {.discardable.} =
  129. try:
  130. moved[field] = val
  131. return true
  132. except Exception:
  133. return false
  134. proc checkInsufficientMaterial(board: Board): bool =
  135. ## Checks for combinations of pieces on a `board`, where no checkmate can be forced
  136. var wp = 0
  137. var wn = 0
  138. var wb = 0
  139. var wr = 0
  140. var wq = 0
  141. var bp = 0
  142. var bn = 0
  143. var bb = 0
  144. var br = 0
  145. var bq = 0
  146. for field in board.low..board.high:
  147. case board.getField(field):
  148. of WPawn:
  149. wp = wp + 1
  150. of BPawn:
  151. bp = bp + 1
  152. of WKnight:
  153. wn = wn + 1
  154. of BKnight:
  155. bn = bn + 1
  156. of WBishop:
  157. wb = wb + 1
  158. of BBishop:
  159. bb = bb + 1
  160. of WRook:
  161. wr = wr + 1
  162. of BRook:
  163. br = br + 1
  164. of WQueen:
  165. wq = wq + 1
  166. of BQueen:
  167. bq = bq + 1
  168. else:
  169. continue
  170. let wpieces = (wp, wn, wb, wr, wq)
  171. let bpieces = (bp, bn, bb, br, bq)
  172. return (wpieces in InsufficientMaterial) and (bpieces in InsufficientMaterial)
  173. proc initBoard(): Board =
  174. ## Create and return a board with pieces in starting position.
  175. let board = [
  176. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  177. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  178. Block, WRook, WKnight, WBishop, WKing, WQueen, WBishop, WKnight, WRook, Block,
  179. Block, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, WPawn, Block,
  180. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  181. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  182. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  183. Block, 0, 0, 0, 0, 0, 0, 0, 0, Block,
  184. Block, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, BPawn, Block,
  185. Block, BRook, BKnight, BBishop, BKing, BQueen, BBishop, BKnight, BRook, Block,
  186. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  187. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block]
  188. return board
  189. proc initBoard(board: array[0..63, int]): Board =
  190. ## Create and return a board with pieces in position of choice
  191. let board = [
  192. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  193. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  194. Block, board[0], board[1], board[2], board[3], board[4], board[5],
  195. board[6], board[7], Block,
  196. Block, board[8], board[9], board[10], board[11], board[12], board[13],
  197. board[14], board[15], Block,
  198. Block, board[16], board[17], board[18], board[19], board[20], board[
  199. 21], board[22], board[23], Block,
  200. Block, board[24], board[25], board[26], board[27], board[28], board[
  201. 29], board[30], board[31], Block,
  202. Block, board[32], board[33], board[34], board[35], board[36], board[
  203. 37], board[38], board[39], Block,
  204. Block, board[40], board[41], board[42], board[43], board[44], board[
  205. 45], board[46], board[47], Block,
  206. Block, board[48], board[49], board[50], board[51], board[52], board[
  207. 53], board[54], board[55], Block,
  208. Block, board[56], board[57], board[58], board[59], board[60], board[
  209. 61], board[62], board[63], Block,
  210. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block,
  211. Block, Block, Block, Block, Block, Block, Block, Block, Block, Block]
  212. return board
  213. proc initMoved(): Moved =
  214. ## Create and return a board of pieces moved.
  215. var moved: Moved
  216. return moved
  217. proc initGame*(): Game =
  218. ## Create and return a Game object.
  219. let game = Game(board: initBoard(), moved: initMoved(),
  220. to_move: Color.White, previousBoard: @[], previousCastleRights: @[])
  221. return game
  222. proc initGame*(board: array[0..63, int], color: Color): Game =
  223. ## Create ad return a Game object based on a position of choice.
  224. let board = initBoard(board)
  225. let compare = initBoard()
  226. var moved = initMoved()
  227. var same_piece: bool
  228. for ind in board.low..board.high:
  229. same_piece = (board[ind] != compare[ind])
  230. moved.setField(ind, same_piece)
  231. let game = Game(board: board, moved: moved,
  232. to_move: color, previousBoard: @[], previousCastleRights: @[])
  233. return game
  234. proc getMove*(start: int, dest: int, prom: int, color: Color): Move =
  235. ## Get a move object from `start` to `dest` with an eventual promition to `prom`
  236. var move = Move(start: start, dest: dest, prom: prom * ord(color), color: color)
  237. if (KnightID > prom or QueenID < prom):
  238. move.prom = QueenID
  239. return move
  240. proc getMove*(start: int, dest: int, color: Color): Move =
  241. ## Get a move object from `start` to `dest` with automatic promition to `queen`
  242. var move = Move(start: start, dest: dest, prom: QueenID * ord(color), color: color)
  243. return move
  244. proc echoBoard*(game: Game, color: Color) =
  245. ## Prints out the given `board` with its pieces as characters and line indices from perspecive of `color`.
  246. var line_str = ""
  247. if (color == Color.Black):
  248. for i in countup(0, len(game.board)-1):
  249. if (game.board.getField(i) == 999):
  250. continue
  251. line_str &= PieceChar[game.board[i]] & " "
  252. if ((i+2) %% 10 == 0):
  253. line_str &= $((int)((i)/10)-1) & "\n"
  254. echo line_str
  255. echo "h g f e d c b a"
  256. else:
  257. for i in countdown(len(game.board)-1, 0):
  258. if (game.board.getField(i) == 999):
  259. continue
  260. line_str &= PieceChar[game.board[i]] & " "
  261. if ((i-1) %% 10 == 0):
  262. line_str &= $((int)((i)/10)-1) & "\n"
  263. echo line_str
  264. echo "a b c d e f g h"
  265. proc fieldToInd*(file: string, line: int): int =
  266. ## Calculate board index from `file` and `line` of a chess board.
  267. try:
  268. return 1+(line+1)*10+FileChar[file]
  269. except IndexDefect, ValueError:
  270. return -1
  271. proc fieldToInd*(field: string): int =
  272. ## Calculate board index from `field` of a chess board.
  273. try:
  274. return fieldToInd($field[0], parseInt($field[1]))
  275. except IndexDefect, ValueError:
  276. return -1
  277. proc indToField*(ind: int): string =
  278. ## Calculate field name from board index `ind`.
  279. let line = (int)ind/10-1
  280. let file_ind = (ind)%%10-1
  281. for file, i in FileChar:
  282. if FileChar[file] == file_ind:
  283. return $file & $line
  284. proc genCastleRights(moved: Moved): CastleRights =
  285. ## Generate rights to castle from given `moved`
  286. let wk = not moved.getField(fieldToInd("e1")) and not moved.getField(
  287. fieldToInd("h1"))
  288. let wq = not moved.getField(fieldToInd("e1")) and not moved.getField(
  289. fieldToInd("a1"))
  290. let bk = not moved.getField(fieldToInd("e8")) and not moved.getField(
  291. fieldToInd("h8"))
  292. let bq = not moved.getField(fieldToInd("e8")) and not moved.getField(
  293. fieldToInd("a8"))
  294. return (wk, wq, bk, bq)
  295. proc notationToMove*(notation: string, color: Color): Move =
  296. ## Convert simplified algebraic chess `notation` to a move object, color of player is `color`.
  297. try:
  298. var move: Move
  299. var start = fieldToInd(notation[0..1])
  300. var dest = fieldToInd(notation[2..3])
  301. move = getMove(start, dest, color)
  302. if (len(notation) > 4):
  303. var promStr = $notation[4]
  304. var prom: int
  305. case promStr:
  306. of "Q":
  307. prom = QueenID * ord(color)
  308. of "R":
  309. prom = RookID * ord(color)
  310. of "B":
  311. prom = BishopID * ord(color)
  312. of "N":
  313. prom = KnightID * ord(color)
  314. move = getMove(start, dest, prom, color)
  315. return move
  316. except IndexError:
  317. var move: Move
  318. return move
  319. proc genBishopDests(game: Game, field: int, color: Color): seq[int] =
  320. ## Generate possible destinations for a bishop with specific `color` located at index `field` of `game`.
  321. ## Returns a sequence of possible indices to move to.
  322. try:
  323. var res = newSeq[int]()
  324. var dest: int
  325. var target: int
  326. for move in Bishop_Moves:
  327. dest = field+move
  328. target = game.board.getField(dest)
  329. while (target != 999 and (ord(color) * target <= 0) or target ==
  330. EnPassantID or target == -EnPassantID):
  331. res.add(dest)
  332. if (ord(color) * target < 0 and ord(color) * target > -EnPassantID):
  333. break
  334. dest = dest+move
  335. target = game.board.getField(dest)
  336. return res
  337. except IndexDefect:
  338. return @[]
  339. proc genRookDests(game: Game, field: int, color: Color): seq[int] =
  340. ## Generate possible destinations for a rook with specific `color` located at index `field` of `game`.
  341. ## Returns a sequence of possible indices to move to.
  342. try:
  343. var res = newSeq[int]()
  344. var dest: int
  345. var target: int
  346. for move in Rook_Moves:
  347. dest = field+move
  348. target = game.board.getField(dest)
  349. while (target != 999 and (ord(color) * target <= 0) or target ==
  350. EnPassantID or target == -EnPassantID):
  351. res.add(dest)
  352. if (ord(color) * target < 0 and ord(color) * target > -EnPassantID):
  353. break
  354. dest = dest+move
  355. target = game.board.getField(dest)
  356. return res
  357. except IndexDefect:
  358. return @[]
  359. proc genQueenDests(game: Game, field: int, color: Color): seq[int] =
  360. ## Generate possible destinations for a queen with specific `color` located at index `field` of `game`.
  361. ## Returns a sequence of possible indices to move to.
  362. try:
  363. var res = newSeq[int]()
  364. var dest: int
  365. var target: int
  366. for move in Queen_Moves:
  367. dest = field+move
  368. target = game.board.getField(dest)
  369. while (target != 999 and (ord(color) * target <= 0) or target ==
  370. EnPassantID or target == -EnPassantID):
  371. res.add(dest)
  372. if (ord(color) * target < 0 and ord(color) * target > -EnPassantID):
  373. break
  374. dest = dest+move
  375. target = game.board.getField(dest)
  376. return res
  377. except IndexDefect:
  378. return @[]
  379. proc genKingCastleDest(game: Game, field: int, color: Color): seq[int] =
  380. ## Generate possible castle destinations for a king with specific `color` located at index `field` of `game`
  381. ## Returns a sequence of possible indices to move to.
  382. try:
  383. var res = newSeq[int]()
  384. var dest: int
  385. var target: int
  386. var half_dest: int
  387. var half_target: int
  388. for castle in King_Moves_White_Castle:
  389. dest = field + castle
  390. target = game.board.getField(dest)
  391. half_dest = field + (int)castle/2
  392. half_target = game.board.getField(half_dest)
  393. if (target == 999 or (target != 0)):
  394. continue
  395. if (half_target == 999 or (half_target != 0)):
  396. continue
  397. res.add(dest)
  398. return res
  399. except IndexDefect:
  400. return @[]
  401. proc genKingDests(game: Game, field: int, color: Color): seq[int] =
  402. ## Generate possible destinations for a king with specific `color` located at index `field` of `game`.
  403. ## Returns a sequence of possible indices to move to.
  404. try:
  405. var res = newSeq[int]()
  406. var dest: int
  407. var target: int
  408. for move in King_Moves:
  409. dest = field + move
  410. target = game.board.getField(dest)
  411. if (target == 999 or (ord(color) * target > 0 and ord(color) * target != EnPassantID)):
  412. continue
  413. res.add(dest)
  414. res.add(game.genKingCastleDest(field, color))
  415. return res
  416. except IndexDefect:
  417. return @[]
  418. proc genKnightDests(game: Game, field: int, color: Color): seq[int] =
  419. ## Generate possible destinations for a knight with specific `color` located at index `field` of `game`.
  420. ## Returns a sequence of possible indices to move to.
  421. try:
  422. var res = newSeq[int]()
  423. var dest: int
  424. var target: int
  425. for move in Knight_Moves:
  426. dest = field + move
  427. target = game.board.getField(dest)
  428. if (target == 999 or (ord(color) * target > 0 and ord(color) * target != EnPassantID)):
  429. continue
  430. res.add(dest)
  431. return res
  432. except IndexDefect:
  433. return @[]
  434. proc genPawnAttackDests(game: Game, field: int, color: Color): seq[int] =
  435. ## Generate possible attack destinations for a pawn with specific `color` located at index `field` of `game`.
  436. ## Returns a sequence of possible indices to move to.
  437. try:
  438. var res = newSeq[int]()
  439. var dest: int
  440. var target: int
  441. for attacks in Pawn_Moves_White_Attack:
  442. dest = field + (attacks * ord(color))
  443. target = game.board.getField(dest)
  444. if (target == 999 or ord(color) * target >= 0):
  445. continue
  446. res.add(dest)
  447. return res
  448. except IndexDefect:
  449. return @[]
  450. proc genPawnDoubleDests(game: Game, field: int, color: Color): seq[int] =
  451. ## Generate possible double destinations for a pawn with specific `color` located at index `field` of `game`.
  452. ## Returns a sequence of possible indices to move to.
  453. try:
  454. var res = newSeq[int]()
  455. var dest: int
  456. var target: int
  457. for doubles in Pawn_Moves_White_Double:
  458. dest = field + doubles * ord(color)
  459. target = game.board.getField(dest)
  460. if (game.moved.getField(field) or (target != 0) or (
  461. game.board.getField(dest+(S*ord(color))) != 0)):
  462. continue
  463. res.add(dest)
  464. return res
  465. except IndexDefect:
  466. return @[]
  467. proc genPawnDests(game: Game, field: int, color: Color): seq[int] =
  468. ## Generate possible destinations for a pawn with specific `color` located at index `field` of `game`.
  469. ## Returns a sequence of possible indices to move to.
  470. try:
  471. var res = newSeq[int]()
  472. var dest: int
  473. var target: int
  474. for move in Pawn_Moves_White:
  475. dest = field + move * ord(color)
  476. target = game.board.getField(dest)
  477. if (target != 0 and target != ord(color) * EnPassantID):
  478. continue
  479. res.add(dest)
  480. res.add(game.genPawnAttackDests(field, color))
  481. res.add(game.genPawnDoubleDests(field, color))
  482. return res
  483. except IndexDefect:
  484. return @[]
  485. proc pieceOn(game: Game, color: Color, sequence: seq[int],
  486. pieceID: int): bool =
  487. ## Check if a piece with `pieceID` of a given `color` is in a field described in a `sequence` in a `game`.
  488. for check in sequence:
  489. if game.board.getField(check) == ord(color) * -1 * pieceID:
  490. return true
  491. return false
  492. proc isAttacked(game: Game, position: int, color: Color): bool =
  493. ## Check if a field is attacked by the opposite of `color` in a `game`.
  494. var attacked = false
  495. attacked = attacked or game.pieceOn(color, game.genPawnAttackDests(
  496. position, color), PawnID)
  497. attacked = attacked or game.pieceOn(color, game.genQueenDests(position,
  498. color), QueenID)
  499. attacked = attacked or game.pieceOn(color, game.genKingDests(position,
  500. color), KingID)
  501. attacked = attacked or game.pieceOn(color, game.genRookDests(position,
  502. color), RookID)
  503. attacked = attacked or game.pieceOn(color, game.genBishopDests(position,
  504. color), BishopID)
  505. attacked = attacked or game.pieceOn(color, game.genKnightDests(position,
  506. color), KnightID)
  507. return attacked
  508. proc isInCheck*(game: Game, color: Color): bool =
  509. ## Check if the King of a given `color` is in check in a `game`.
  510. var king_pos: int
  511. for i in countup(0, game.board.high):
  512. if game.board.getField(i) == ord(color) * KingID:
  513. king_pos = i
  514. return game.isAttacked(king_pos, color)
  515. proc uncheckedMove(game: var Game, start: int, dest: int): bool {.discardable.} =
  516. ## Moves a piece if possible from `start` position to `dest` position.
  517. ## Doesnt check boundaries, checks, movement.
  518. ## returns true if the piece moved, else false
  519. try:
  520. let piece = game.board.getField(start)
  521. if game.board.setField(start, 0):
  522. if game.board.setField(dest, piece):
  523. game.moved.setField(start, true)
  524. game.moved.setField(dest, true)
  525. return true
  526. else:
  527. game.board.setField(start, piece)
  528. except IndexDefect, ValueError:
  529. return false
  530. proc moveLeadsToCheck(game: Game, start: int, dest: int,
  531. color: Color): bool =
  532. ## Checks in a `game` if a move from `start` to `dest` puts the `color` king in check.
  533. var check = game
  534. check.uncheckedMove(start, dest)
  535. return check.isInCheck(color)
  536. proc removeEnPassant(board: var Board, color: Color): void =
  537. ## Removes every en passant of given `color` from the `game`.
  538. for field in board.low..board.high:
  539. if board.getField(field) == ord(color) * EnPassantID:
  540. board.setField(field, 0)
  541. proc genLegalKnightMoves(game: Game, field: int, color: Color): seq[Move] =
  542. ## Generates all legal knight moves starting from `field` in a `game` for a `color`.
  543. if game.board.getField(field) != KnightID * ord(color):
  544. return @[]
  545. var res = newSeq[Move]()
  546. var moves = game.genKnightDests(field, color)
  547. for dest in moves:
  548. if not game.moveLeadsToCheck(field, dest, color):
  549. res.add(getMove(field, dest, color))
  550. return res
  551. proc genLegalBishopMoves(game: Game, field: int, color: Color): seq[Move] =
  552. ## Generates all legal bishop moves starting from `field` in a `game` for a `color`.
  553. if game.board.getField(field) != BishopID * ord(color):
  554. return @[]
  555. var res = newSeq[Move]()
  556. var moves = game.genBishopDests(field, color)
  557. for dest in moves:
  558. if not game.moveLeadsToCheck(field, dest, color):
  559. res.add(getMove(field, dest, color))
  560. return res
  561. proc genLegalRookMoves(game: Game, field: int, color: Color): seq[Move] =
  562. ## Generates all legal rook moves starting from `field` in a `game` for a `color`.
  563. if game.board.getField(field) != RookID * ord(color):
  564. return @[]
  565. var res = newSeq[Move]()
  566. var moves = game.genRookDests(field, color)
  567. for dest in moves:
  568. if not game.moveLeadsToCheck(field, dest, color):
  569. res.add(getMove(field, dest, color))
  570. return res
  571. proc genLegalQueenMoves(game: Game, field: int, color: Color): seq[Move] =
  572. ## Generates all legal queen moves starting from `field` in a `game` for a `color`.
  573. if game.board.getField(field) != QueenID * ord(color):
  574. return @[]
  575. var res = newSeq[Move]()
  576. var moves = game.genQueenDests(field, color)
  577. for dest in moves:
  578. if not game.moveLeadsToCheck(field, dest, color):
  579. res.add(getMove(field, dest, color))
  580. return res
  581. proc genLegalKingMoves(game: Game, field: int, color: Color): seq[Move] =
  582. ## Generates all legal king moves starting from `field` in a `game` for a `color`.
  583. if game.board.getField(field) != KingID * ord(color):
  584. return @[]
  585. var res = newSeq[Move]()
  586. var moves = game.genKingDests(field, color)
  587. for dest in moves:
  588. if field - dest == W+W and game.isAttacked(dest+W, color):
  589. continue
  590. if field - dest == E+E and game.isAttacked(dest+E, color):
  591. continue
  592. if not game.moveLeadsToCheck(field, dest, color):
  593. res.add(getMove(field, dest, color))
  594. return res
  595. proc genPawnPromotion(move: Move, color: Color): seq[Move] =
  596. ## Generate all possible promotions of a `move` by `color`.
  597. var promotions = newSeq[Move]()
  598. let start = move.start
  599. let dest = move.dest
  600. if (90 < dest and dest < 99) or (20 < dest and dest < 29):
  601. for piece in KnightID..QueenID:
  602. promotions.add(getMove(start, dest, piece, color))
  603. return promotions
  604. proc genLegalPawnMoves(game: Game, field: int, color: Color): seq[Move] =
  605. ## Generates all legal pawn moves starting from `field` in a `game` for a `color`.
  606. if game.board.getField(field) != PawnID * ord(color):
  607. return @[]
  608. var res = newSeq[Move]()
  609. var moves = game.genPawnDests(field, color)
  610. for dest in moves:
  611. if not game.moveLeadsToCheck(field, dest, color):
  612. var promotions = genPawnPromotion(getMove(field, dest, color), color)
  613. if promotions != @[]:
  614. res.add(promotions)
  615. else:
  616. res.add(getMove(field, dest, color))
  617. return res
  618. proc genLegalMoves*(game: Game, field: int, color: Color): seq[Move] =
  619. ## Generates all legal moves starting from `field` in a `game` for a `color`.
  620. var legal_moves = newSeq[Move]()
  621. var target = ord(color) * game.board.getField(field)
  622. if 0 < target and target < EnPassantID:
  623. legal_moves = case target:
  624. of PawnID:
  625. game.genLegalPawnMoves(field, color)
  626. of KnightID:
  627. game.genLegalKnightMoves(field, color)
  628. of BishopID:
  629. game.genLegalBishopMoves(field, color)
  630. of RookID:
  631. game.genLegalRookMoves(field, color)
  632. of QueenID:
  633. game.genLegalQueenMoves(field, color)
  634. of KingID:
  635. game.genLegalKingMoves(field, color)
  636. else:
  637. @[]
  638. return legal_moves
  639. proc genLegalMoves*(game: Game, color: Color): seq[Move] =
  640. ## Generates all legal moves in a `game` for a `color`.
  641. var legal_moves = newSeq[Move]()
  642. for field in game.board.low..game.board.high:
  643. legal_moves.add(game.genLegalMoves(field, color))
  644. return legal_moves
  645. proc castling(game: var Game, kstart: int, dest_kingside: bool,
  646. color: Color): bool {.discardable.} =
  647. ## Tries to castle in a given `game` with the king of a given `color` from `start`.
  648. ## `dest_kingside` for kingside castling, else castling is queenside.
  649. ## This process checks for the legality of the move and performs the switch of `game.to_move`
  650. try:
  651. if game.toMove != color:
  652. return false
  653. var kdest = kstart
  654. var rstart: int
  655. var rdest: int
  656. if (dest_kingside):
  657. kdest = kstart + (E+E)
  658. rstart = kstart + (E+E+E)
  659. rdest = rstart + (W+W)
  660. else:
  661. rstart = kstart + (W+W+W+W)
  662. rdest = rstart + (E+E+E)
  663. kdest = kstart + (W+W)
  664. if not game.moved.getField(kstart) and not game.moved.getField(rstart):
  665. var check = false
  666. if (dest_kingside):
  667. check = check or game.isAttacked(kstart, color)
  668. check = check or game.isAttacked(kstart+(E), color)
  669. check = check or game.isAttacked(kstart+(E+E), color)
  670. else:
  671. check = check or game.isAttacked(kstart, color)
  672. check = check or game.isAttacked(kstart+(W), color)
  673. check = check or game.isAttacked(kstart+(W+W), color)
  674. if check:
  675. return false
  676. game.uncheckedMove(kstart, kdest)
  677. game.uncheckedMove(rstart, rdest)
  678. game.toMove = Color(ord(game.toMove)*(-1))
  679. return true
  680. return false
  681. except IndexDefect, ValueError:
  682. return false
  683. proc getPrevBoard*(game: Game): seq[Board] =
  684. return game.previousBoard
  685. proc checkedMove*(game: var Game, move: Move): bool {.discardable.} =
  686. ## Tries to make a move in a given `game` with the piece of a given `color` from `start` to `dest`.
  687. ## This process checks for the legality of the move and performs the switch of `game.to_move`
  688. try:
  689. let start = move.start
  690. let dest = move.dest
  691. let color = move.color
  692. let prom = move.prom
  693. if game.toMove != color:
  694. return false
  695. var sequence = newSeq[Move]()
  696. let piece = game.board.getField(start)
  697. var create_en_passant = false
  698. var captured_en_passant = false
  699. var move: Move
  700. move = getMove(start, dest, color)
  701. if (piece == PawnID * ord(color)):
  702. create_en_passant = dest in game.genPawnDoubleDests(start, color)
  703. captured_en_passant = (game.board.getField(dest) == -1 * ord(color) * EnPassantID)
  704. sequence.add(game.genLegalMoves(start, color))
  705. if (move in sequence):
  706. game.board.removeEnPassant(color)
  707. if (piece == KingID * ord(color) and (start - dest == (W+W))):
  708. return game.castling(start, true, color)
  709. elif (piece == KingID * ord(color) and (start - dest == (E+E))):
  710. return game.castling(start, false, color)
  711. else:
  712. game.uncheckedMove(start, dest)
  713. game.toMove = Color(ord(game.toMove)*(-1))
  714. if create_en_passant:
  715. game.board.setField(dest-(N*ord(color)), EnPassantID * ord(color))
  716. if captured_en_passant:
  717. game.board.setField(dest-(N*ord(color)), 0)
  718. if ((90 < dest and dest < 99) or (20 < dest and dest < 29)) and
  719. game.board.getField(dest) == PawnID * ord(color):
  720. game.board.setField(dest, prom)
  721. var prevBoard = game.previousBoard
  722. var prevCastle = game.previousCastleRights
  723. game.previousBoard.add(game.board)
  724. game.previousCastleRights.add(game.moved.genCastleRights())
  725. return true
  726. except IndexDefect, ValueError:
  727. return false
  728. proc hasNoMoves(game: Game, color: Color): bool =
  729. ## Checks if a player of a given `color` has no legal moves in a `game`.
  730. return (game.genLegalMoves(color) == @[])
  731. proc isCheckmate*(game: Game, color: Color): bool =
  732. ## Checks if a player of a given `color` in a `game` is checkmate.
  733. return game.hasNoMoves(color) and game.isInCheck(color)
  734. proc threeMoveRep(game: Game): bool =
  735. ## Checks if a `rep`-times repitition happened on the last move of the `game`.
  736. var lastState = game.previousBoard[game.previousBoard.high]
  737. var lastCastleRights = game.previousCastleRights[game.previousBoard.high]
  738. var reps = 0
  739. for stateInd in (game.previousBoard.low)..(game.previousBoard.high):
  740. if (game.previousBoard[stateInd] == lastState and game.previousCastleRights[
  741. stateInd] == lastCastleRights):
  742. reps = reps + 1
  743. return reps >= 3
  744. proc isDrawClaimable*(game: Game): bool =
  745. ## Checks if a draw is claimable by either player.
  746. return game.threeMoveRep()
  747. proc isStalemate*(game: Game, color: Color): bool =
  748. ## Checks if a player of a given `color` in a `game` is stalemate.
  749. return (game.hasNoMoves(color) and not game.isInCheck(color)) or
  750. game.board.checkInsufficientMaterial()