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.

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