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.

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