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.

839 lines
31 KiB

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